diff --git a/build.gradle b/build.gradle index 9317ee67..73ef8915 100644 --- a/build.gradle +++ b/build.gradle @@ -55,7 +55,10 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-security' testImplementation 'org.springframework.security:spring-security-test' - //s3 + // spring security, thymeleaf + implementation("org.thymeleaf.extras:thymeleaf-extras-springsecurity6") + + //s3 implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' //Jwt diff --git a/src/main/java/TtattaBackend/ttatta/config/security/CustomAuthenticationSuccessHandler.java b/src/main/java/TtattaBackend/ttatta/config/security/CustomAuthenticationSuccessHandler.java new file mode 100644 index 00000000..d9c75f35 --- /dev/null +++ b/src/main/java/TtattaBackend/ttatta/config/security/CustomAuthenticationSuccessHandler.java @@ -0,0 +1,59 @@ +package TtattaBackend.ttatta.config.security; + +import TtattaBackend.ttatta.apiPayload.exception.handler.ExceptionHandler; +import TtattaBackend.ttatta.domain.LocationAccessLogs; +import TtattaBackend.ttatta.domain.Users; +import TtattaBackend.ttatta.domain.enums.UserRole; +import TtattaBackend.ttatta.jwt.JwtUtils; +import TtattaBackend.ttatta.repository.LocationAccessLogRepository; +import TtattaBackend.ttatta.repository.UserRepository; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static TtattaBackend.ttatta.apiPayload.code.status.ErrorStatus.USER_NOT_FOUND; + +@Component +@RequiredArgsConstructor +public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + + private final UserRepository userRepository; + private final JwtUtils jwtUtils; + private final LocationAccessLogRepository locationAccessLogRepository; + + @Override + public void onAuthenticationSuccess( + HttpServletRequest request, + HttpServletResponse response, + Authentication authentication + ) throws IOException { + // 로그인 성공 후 처리할 로직을 여기에 작성합니다. + // User의 Role을 포함한 JWT 토큰 생성 + Users getUser = userRepository.findByUsername(authentication.getName()) + .orElseThrow(() -> new UsernameNotFoundException("해당 아이디를 가진 유저가 존재하지 않습니다: " + authentication.getName())); + Map valueMap = new HashMap<>(); + valueMap.put("userId", getUser.getId()); + valueMap.put("role", getUser.getRole()); + String accessToken = jwtUtils.generateToken(valueMap, 15); + // JWT 토큰을 HTTP 응답 헤더에 추가 + var cookie = org.springframework.http.ResponseCookie.from("ACCESS_TOKEN", accessToken) + .httpOnly(true).secure(true).sameSite("Lax").path("/") + .maxAge(java.time.Duration.ofMinutes(30)).build(); + response.addHeader(org.springframework.http.HttpHeaders.SET_COOKIE, cookie.toString()); + if (getUser.getRole().equals(UserRole.ADMIN)) response.sendRedirect("/admin/location-log"); + else if (getUser.getRole().equals(UserRole.SUPER_ADMIN)) response.sendRedirect("/super/admin/home"); + else response.sendRedirect("/admin/login?error=insufficient_role"); // 관리자 권한이 없는 경우: 로그인 페이지로 재이동 (사유 전달) + // 관리자 접속 로그 저장 + locationAccessLogRepository.save(LocationAccessLogs.builder() + .adminId(getUser.getId().toString()) + .build()); + } +} diff --git a/src/main/java/TtattaBackend/ttatta/config/security/JwtAuthenticationFilter.java b/src/main/java/TtattaBackend/ttatta/config/security/JwtAuthenticationFilter.java index 5e9314bc..c096c2c1 100644 --- a/src/main/java/TtattaBackend/ttatta/config/security/JwtAuthenticationFilter.java +++ b/src/main/java/TtattaBackend/ttatta/config/security/JwtAuthenticationFilter.java @@ -7,6 +7,7 @@ import io.jsonwebtoken.Claims; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -25,7 +26,7 @@ @RequiredArgsConstructor @Component -@Slf4j //??? +@Slf4j public class JwtAuthenticationFilter extends OncePerRequestFilter { @Value("${jwt.JWT_HEADER}") @@ -42,11 +43,14 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { "/items", "/users/verificate/kakao", "/users/admin/**", + "/admin/login", + "/loginProc" // "/refresh", "/", // "/index.html" }; private final JwtUtils jwtUtils; private final RedisTemplate redisTemplate; + private final String cookieName = "ACCESS_TOKEN"; private static void checkAuthorizationHeader(String header) { if(header == null) { @@ -104,8 +108,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse try { log.info("------------------------------------------------------"); - checkAuthorizationHeader(authHeader); // header 가 올바른 형식인지 체크 - String accessToken = JwtUtils.getTokenFromHeader(authHeader); + // 타임리프 페이지 인가 처리 + String accessToken = resolveToken(request); jwtUtils.validateToken(accessToken); // 토큰 검증 jwtUtils.isTokenBlacklisted(authHeader); // 🚨 블랙리스트 확인 } catch (Exception e) { @@ -129,8 +133,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse log.info("--------------------------- JwtVerifyFilter ---------------------------"); try { - checkAuthorizationHeader(authHeader); // header 가 올바른 형식인지 체크 - String token = JwtUtils.getTokenFromHeader(authHeader); + // 타임리프 페이지 인가 처리 + String token = resolveToken(request); + jwtUtils.validateToken(token); // 토큰 검증 jwtUtils.isExpired(token); // 토큰 만료 검증 @@ -154,4 +159,23 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse printWriter.close(); } } + + private String resolveToken(HttpServletRequest request) { + // 1) Authorization 헤더: Bearer + String authHeader = request.getHeader(jwtHeader); + if (authHeader != null && authHeader.startsWith("Bearer ")) { + checkAuthorizationHeader(authHeader); // header 가 올바른 형식인지 체크 + return JwtUtils.getTokenFromHeader(authHeader); + } + // 2) 쿠키: ACCESS_TOKEN + if (request.getCookies() != null) { + for (Cookie c : request.getCookies()) { + if (cookieName.equals(c.getName())) { + // 쿠키에 바로 토큰을 담은 경우 + return c.getValue(); + } + } + } + return null; + } } diff --git a/src/main/java/TtattaBackend/ttatta/config/security/SecurityConfig.java b/src/main/java/TtattaBackend/ttatta/config/security/SecurityConfig.java index 2ba2eacc..0424cd1e 100644 --- a/src/main/java/TtattaBackend/ttatta/config/security/SecurityConfig.java +++ b/src/main/java/TtattaBackend/ttatta/config/security/SecurityConfig.java @@ -10,6 +10,7 @@ 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.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @EnableWebSecurity @@ -18,37 +19,54 @@ public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final AuthenticationSuccessHandler customAuthenticationSuccessHandler; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) // 추가해주어야함. // 폼 로그인 비활성화 - .formLogin(AbstractHttpConfigurer::disable) + .formLogin( (formLogin) -> formLogin + .loginPage("/admin/login") + .loginProcessingUrl("/loginProc") + .usernameParameter("email") + .passwordParameter("password") + .defaultSuccessUrl("/admin") + .successHandler(customAuthenticationSuccessHandler) +// .failureHandler(customFailureHandler) + .permitAll() + ) + .logout( (logout) -> logout + .logoutUrl("/logoutProc") + .logoutSuccessUrl("/admin/login") + .permitAll() + ) // HTTP Basic 인증 비활성화 .httpBasic(AbstractHttpConfigurer::disable) .sessionManagement(session -> session - .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션을 Stateless로 설정 + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ) // 세션을 Stateless로 설정 .authorizeHttpRequests((requests) -> requests - .requestMatchers("/**").permitAll()) + .requestMatchers("/admin/**").hasAnyRole("SUPER_ADMIN", "ADMIN") // == hasAuthority("ROLE_ADMIN") + .requestMatchers("/super/admin/**").hasRole("SUPER_ADMIN") // == hasAuthority("ROLE_ADMIN") + .requestMatchers("/**").permitAll() +// .requestMatchers( +// "/users/signup", +// "/users/signup/**", +// "/users/signin", +// "/users/signin/**", +// "/users/testuser", +// "/users/find/**", +// "/swagger-ui/**", +// "/v3/**", +// "/items", +// "/users/verificate/kakao", +// "/users/admin/**", +// "/admin/login", +// "/loginProc" +// ).permitAll() + ) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - -// .authorizeHttpRequests((requests) -> requests -// .requestMatchers("/", "/home", "/signup", "/css/**").permitAll() -// .requestMatchers("/admin/**").hasRole("ADMIN") -// .anyRequest().authenticated() -// ) -// .formLogin((form) -> form -// .loginPage("/login") -// .defaultSuccessUrl("/home", true) -// .permitAll() -// ) -// .logout((logout) -> logout -// .logoutUrl("/logout") -// .logoutSuccessUrl("/login?logout") -// .permitAll() -// ); - return http.build(); } diff --git a/src/main/java/TtattaBackend/ttatta/config/security/SecurityUtil.java b/src/main/java/TtattaBackend/ttatta/config/security/SecurityUtil.java index c612e036..537d234a 100644 --- a/src/main/java/TtattaBackend/ttatta/config/security/SecurityUtil.java +++ b/src/main/java/TtattaBackend/ttatta/config/security/SecurityUtil.java @@ -1,7 +1,9 @@ package TtattaBackend.ttatta.config.security; +import TtattaBackend.ttatta.domain.enums.UserRole; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; @Slf4j @@ -22,4 +24,19 @@ public static Long getCurrentUserId() { return Long.parseLong(authentication.getName()); } + + public static UserRole getCurrentUserRole() { + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication == null || authentication.getAuthorities() == null || !authentication.isAuthenticated()) { + throw new RuntimeException("Security Context 에 인증 정보가 없습니다."); + } + UserRole role = authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) // ROLE_ADMIN + .map(r -> r.replaceFirst("^ROLE_", "")) // ADMIN + .map(UserRole::valueOf) + .findFirst() + .orElse(null); + return role; + } } diff --git a/src/main/java/TtattaBackend/ttatta/converter/LocationLogConverter.java b/src/main/java/TtattaBackend/ttatta/converter/LocationLogConverter.java new file mode 100644 index 00000000..853f1d64 --- /dev/null +++ b/src/main/java/TtattaBackend/ttatta/converter/LocationLogConverter.java @@ -0,0 +1,15 @@ +package TtattaBackend.ttatta.converter; + +import TtattaBackend.ttatta.domain.LocationLogs; +import TtattaBackend.ttatta.domain.Users; + +public class LocationLogConverter { + + public static LocationLogs toLocationsLogs(Users user, String provisionalService, String recipient) { + return LocationLogs.builder() + .target(user.getId().toString()) + .provisionalService(provisionalService) + .recipient(recipient) + .build(); + } +} diff --git a/src/main/java/TtattaBackend/ttatta/converter/UserConverter.java b/src/main/java/TtattaBackend/ttatta/converter/UserConverter.java index e1f31b48..98f3fe14 100644 --- a/src/main/java/TtattaBackend/ttatta/converter/UserConverter.java +++ b/src/main/java/TtattaBackend/ttatta/converter/UserConverter.java @@ -3,6 +3,7 @@ import TtattaBackend.ttatta.domain.Users; import TtattaBackend.ttatta.domain.enums.IsAvailable; import TtattaBackend.ttatta.domain.enums.LoginType; +import TtattaBackend.ttatta.domain.enums.UserRole; import TtattaBackend.ttatta.domain.enums.UserStatus; import TtattaBackend.ttatta.web.dto.UserRequestDTO; import TtattaBackend.ttatta.web.dto.UserResponseDTO; @@ -18,6 +19,7 @@ public static Users toUsers(UserRequestDTO.SignUpRequestDTO request) { .username(request.getUsername()) .loginType(LoginType.REGULAR) .diaryCategoriesList(new ArrayList<>()) + .role(UserRole.USER) .build(); } @@ -31,6 +33,7 @@ public static Users toKakaoUsers(String sub) { .diaryCategoriesList(new ArrayList<>()) .providerId(sub) .loginType(LoginType.KAKAO) + .role(UserRole.USER) .build(); } diff --git a/src/main/java/TtattaBackend/ttatta/domain/LocationAccessLogs.java b/src/main/java/TtattaBackend/ttatta/domain/LocationAccessLogs.java new file mode 100644 index 00000000..f720c8b7 --- /dev/null +++ b/src/main/java/TtattaBackend/ttatta/domain/LocationAccessLogs.java @@ -0,0 +1,25 @@ +package TtattaBackend.ttatta.domain; + + +import TtattaBackend.ttatta.domain.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +@Entity +@Getter +@DynamicUpdate +@DynamicInsert +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class LocationAccessLogs extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 20) + private String adminId; // 접근권한자 식별 정보 +} diff --git a/src/main/java/TtattaBackend/ttatta/domain/LocationLogs.java b/src/main/java/TtattaBackend/ttatta/domain/LocationLogs.java new file mode 100644 index 00000000..dbe986eb --- /dev/null +++ b/src/main/java/TtattaBackend/ttatta/domain/LocationLogs.java @@ -0,0 +1,33 @@ +package TtattaBackend.ttatta.domain; + +import TtattaBackend.ttatta.domain.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +@Entity +@Getter +@DynamicUpdate +@DynamicInsert +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class LocationLogs extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 50) + private String target; // 대상 + + @Column(nullable = false, columnDefinition = "VARCHAR(20) DEFAULT 'Android'") + private String acquisitionPath; // 취득 경로 + + @Column(nullable = false, length = 50) + private String provisionalService; // 제공 서비스 + + @Column(length = 20) + private String recipient; // 제공받는 자 +} diff --git a/src/main/java/TtattaBackend/ttatta/domain/Users.java b/src/main/java/TtattaBackend/ttatta/domain/Users.java index f972c3bc..da62ed74 100644 --- a/src/main/java/TtattaBackend/ttatta/domain/Users.java +++ b/src/main/java/TtattaBackend/ttatta/domain/Users.java @@ -3,6 +3,7 @@ import TtattaBackend.ttatta.domain.common.BaseEntity; import TtattaBackend.ttatta.domain.enums.Gender; import TtattaBackend.ttatta.domain.enums.LoginType; +import TtattaBackend.ttatta.domain.enums.UserRole; import TtattaBackend.ttatta.domain.enums.UserStatus; import TtattaBackend.ttatta.domain.mapping.OwnedItems; import jakarta.persistence.*; @@ -71,6 +72,10 @@ public class Users extends BaseEntity { @Column(columnDefinition = "TEXT") private String fcmToken; + @Enumerated(EnumType.STRING) + @Column(columnDefinition = "VARCHAR(11)") + private UserRole role; + // 로그인 관련 // private LocalDateTime lastLogin; diff --git a/src/main/java/TtattaBackend/ttatta/domain/enums/UserRole.java b/src/main/java/TtattaBackend/ttatta/domain/enums/UserRole.java new file mode 100644 index 00000000..2b7bcb38 --- /dev/null +++ b/src/main/java/TtattaBackend/ttatta/domain/enums/UserRole.java @@ -0,0 +1,5 @@ +package TtattaBackend.ttatta.domain.enums; + +public enum UserRole { + SUPER_ADMIN, ADMIN, USER +} diff --git a/src/main/java/TtattaBackend/ttatta/jwt/JwtUtils.java b/src/main/java/TtattaBackend/ttatta/jwt/JwtUtils.java index ab2f317f..35f5c7a1 100644 --- a/src/main/java/TtattaBackend/ttatta/jwt/JwtUtils.java +++ b/src/main/java/TtattaBackend/ttatta/jwt/JwtUtils.java @@ -13,16 +13,14 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; import java.time.ZonedDateTime; -import java.util.Collections; -import java.util.Date; -import java.util.Map; -import java.util.Set; +import java.util.*; @Component @RequiredArgsConstructor @@ -57,10 +55,12 @@ public Authentication getAuthentication(String token) { // context에 넣을 Aut Map claims = validateToken(token); System.out.println("userId type: " + (claims.get("userId") != null ? claims.get("userId").getClass().getName() : "null")); -// String email = (String) claims.get("email"); Long userId = ((Integer) claims.get("userId")).longValue(); + String userRole = "ROLE_" + ((String) claims.get("role")).toString(); + List authorities = + Collections.singletonList(new SimpleGrantedAuthority(userRole)); - return new UsernamePasswordAuthenticationToken(userId, null, Collections.emptyList()); + return new UsernamePasswordAuthenticationToken(userId, null, authorities); } public Map validateToken(String token) { // static 제거 diff --git a/src/main/java/TtattaBackend/ttatta/repository/LocationAccessLogRepository.java b/src/main/java/TtattaBackend/ttatta/repository/LocationAccessLogRepository.java new file mode 100644 index 00000000..bef8843e --- /dev/null +++ b/src/main/java/TtattaBackend/ttatta/repository/LocationAccessLogRepository.java @@ -0,0 +1,32 @@ +package TtattaBackend.ttatta.repository; + +import TtattaBackend.ttatta.domain.LocationAccessLogs; +import TtattaBackend.ttatta.domain.LocationLogs; +import io.lettuce.core.dynamic.annotation.Param; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; + +@Repository + +public interface LocationAccessLogRepository extends JpaRepository { + @Query( + "SELECT la FROM LocationAccessLogs la " + + "WHERE (:from IS NULL OR la.updatedAt >= :from) " + + "AND (:to IS NULL OR la.updatedAt < :to) " + + "AND (" + + " :keyword IS NULL OR :keyword = '' OR " + + " LOWER(COALESCE(la.adminId, '')) LIKE LOWER(CONCAT('%', :keyword, '%'))" + + ")" + ) + Page searchWithKeywordAndDate( + Pageable pageable, + @Param("from") LocalDateTime from, + @Param("to") LocalDateTime to, + @Param("keyword") String keyword + ); +} diff --git a/src/main/java/TtattaBackend/ttatta/repository/LocationLogRepository.java b/src/main/java/TtattaBackend/ttatta/repository/LocationLogRepository.java new file mode 100644 index 00000000..f3ca62a8 --- /dev/null +++ b/src/main/java/TtattaBackend/ttatta/repository/LocationLogRepository.java @@ -0,0 +1,33 @@ +package TtattaBackend.ttatta.repository; + +import TtattaBackend.ttatta.domain.LocationLogs; +import io.lettuce.core.dynamic.annotation.Param; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; + +@Repository +public interface LocationLogRepository extends JpaRepository { + @Query( + "SELECT l FROM LocationLogs l " + + "WHERE (:from IS NULL OR l.createdAt >= :from) " + + "AND (:to IS NULL OR l.createdAt < :to) " + + "AND (" + + " :keyword IS NULL OR :keyword = '' OR " + + " LOWER(COALESCE(l.provisionalService, '')) LIKE LOWER(CONCAT('%', :keyword, '%')) OR " + + " LOWER(COALESCE(l.target, '')) LIKE LOWER(CONCAT('%', :keyword, '%')) OR " + + " LOWER(COALESCE(l.recipient, '')) LIKE LOWER(CONCAT('%', :keyword, '%'))" + + ")" + ) + Page searchWithKeywordAndDate( + Pageable pageable, + @Param("from") LocalDateTime from, + @Param("to") LocalDateTime to, + @Param("keyword") String keyword + ); +} diff --git a/src/main/java/TtattaBackend/ttatta/service/AdminPageService/AdminPageQueryService.java b/src/main/java/TtattaBackend/ttatta/service/AdminPageService/AdminPageQueryService.java new file mode 100644 index 00000000..1f001130 --- /dev/null +++ b/src/main/java/TtattaBackend/ttatta/service/AdminPageService/AdminPageQueryService.java @@ -0,0 +1,10 @@ +package TtattaBackend.ttatta.service.AdminPageService; + +import TtattaBackend.ttatta.domain.LocationLogs; +import org.springframework.data.domain.Page; + +import java.time.LocalDate; + +public interface AdminPageQueryService { + Page search(int page, int size, String keyword, LocalDate fromDate, LocalDate toDate); +} diff --git a/src/main/java/TtattaBackend/ttatta/service/AdminPageService/AdminPageQueryServiceImpl.java b/src/main/java/TtattaBackend/ttatta/service/AdminPageService/AdminPageQueryServiceImpl.java new file mode 100644 index 00000000..3f5c09f6 --- /dev/null +++ b/src/main/java/TtattaBackend/ttatta/service/AdminPageService/AdminPageQueryServiceImpl.java @@ -0,0 +1,34 @@ +package TtattaBackend.ttatta.service.AdminPageService; + +import TtattaBackend.ttatta.domain.LocationLogs; +import TtattaBackend.ttatta.repository.LocationLogRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Service +@RequiredArgsConstructor +public class AdminPageQueryServiceImpl implements AdminPageQueryService { + + private final LocationLogRepository locationLogRepository; + + @Override + public Page search(int page, int size, String keyword, LocalDate fromDate, LocalDate toDate) { + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); + LocalDateTime safeMin = LocalDateTime.of(1000, 1, 1, 0, 0); + LocalDateTime safeMax = LocalDateTime.of(9999, 12, 31, 23, 59, 59); + LocalDateTime from = (fromDate != null) ? fromDate.atStartOfDay() : safeMin; + LocalDateTime toEx = (toDate != null) ? toDate.plusDays(1).atStartOfDay() : safeMax; + String kw = (keyword != null && !keyword.isBlank()) ? keyword.trim() : ""; + + return locationLogRepository.searchWithKeywordAndDate( + pageable, from, toEx, kw + ); + } +} diff --git a/src/main/java/TtattaBackend/ttatta/service/AlarmService/AlarmCommandServiceImpl.java b/src/main/java/TtattaBackend/ttatta/service/AlarmService/AlarmCommandServiceImpl.java index 0415cc0e..074bb232 100644 --- a/src/main/java/TtattaBackend/ttatta/service/AlarmService/AlarmCommandServiceImpl.java +++ b/src/main/java/TtattaBackend/ttatta/service/AlarmService/AlarmCommandServiceImpl.java @@ -4,6 +4,7 @@ import TtattaBackend.ttatta.apiPayload.exception.handler.ExceptionHandler; import TtattaBackend.ttatta.config.security.SecurityUtil; import TtattaBackend.ttatta.converter.AlarmConverter; +import TtattaBackend.ttatta.converter.LocationLogConverter; import TtattaBackend.ttatta.domain.*; import TtattaBackend.ttatta.domain.enums.MemoryDiaryAlarmStatus; import TtattaBackend.ttatta.domain.enums.IsActive; @@ -44,6 +45,7 @@ public class AlarmCommandServiceImpl implements AlarmCommandService { private final MemoryDiaryAlarmRepository memoryDiaryAlarmRepository; private final ChallengeRemindAlarmRepository challengeRemindAlarmRepository; private final DailySummaryAlarmRepository dailySummaryAlarmRepository; + private final LocationLogRepository locationLogRepository; @Override @Transactional @@ -227,9 +229,11 @@ public void setMemoryDiaryAlarmStatus(MemoryDiaryAlarmStatus memoryDiaryAlarmSta if (memoryDiaryAlarmStatus == MemoryDiaryAlarmStatus.ON) { // 현재 상태가 OFF 상태인지 확인하는 로직이 필요할까 getMemoryDiaryAlarm.updateIsActive(IsActive.ON); + locationLogRepository.save(LocationLogConverter.toLocationsLogs(getUser, "위치기반리마인드 알림 서비스 / 알림 on", null)); } else if (memoryDiaryAlarmStatus == MemoryDiaryAlarmStatus.OFF) { // 현재상태가 ON 상태인지 확인하는 로직이 필요할까 getMemoryDiaryAlarm.updateIsActive(IsActive.OFF); + locationLogRepository.save(LocationLogConverter.toLocationsLogs(getUser, "위치기반리마인드 알림 서비스 / 알림 off", null)); } } diff --git a/src/main/java/TtattaBackend/ttatta/service/DiaryService/DiaryCommandServiceImpl.java b/src/main/java/TtattaBackend/ttatta/service/DiaryService/DiaryCommandServiceImpl.java index 2035810d..8a78a870 100644 --- a/src/main/java/TtattaBackend/ttatta/service/DiaryService/DiaryCommandServiceImpl.java +++ b/src/main/java/TtattaBackend/ttatta/service/DiaryService/DiaryCommandServiceImpl.java @@ -4,6 +4,7 @@ import TtattaBackend.ttatta.aws.s3.AmazonS3Manager; import TtattaBackend.ttatta.config.security.SecurityUtil; import TtattaBackend.ttatta.converter.DiaryConverter; +import TtattaBackend.ttatta.converter.LocationLogConverter; import TtattaBackend.ttatta.domain.*; import TtattaBackend.ttatta.repository.*; import TtattaBackend.ttatta.security.DecryptedLocation; @@ -29,15 +30,13 @@ @Service @RequiredArgsConstructor public class DiaryCommandServiceImpl implements DiaryCommandService { - private final DiaryRepository diaryRepository; + private final DiaryRepository diaryRepository; private final UserRepository userRepository; - private final DiaryCategoryRepository diaryCategoryRepository; - private final AmazonS3Manager s3Manager; - private final DiaryPhotosRepository diaryPhotosRepository; + private final LocationLogRepository locationLogRepository; // 암호화 저장용 private final EnvelopeCryptoService envelopeCryptoService; @@ -86,6 +85,8 @@ public Diaries save(DiaryRequestDTO.PostDTO request) { diaryPhotos.setDiaries(savedDiaries); diaryPhotosRepository.save(diaryPhotos); + // 위치정보 활용 로그 저장 + locationLogRepository.save(LocationLogConverter.toLocationsLogs(user, "위치기반 일기 저장 서비스", null)); return savedDiaries; } diff --git a/src/main/java/TtattaBackend/ttatta/service/DiaryService/DiaryQueryServiceImpl.java b/src/main/java/TtattaBackend/ttatta/service/DiaryService/DiaryQueryServiceImpl.java index ebe3ffa3..ce2ee900 100644 --- a/src/main/java/TtattaBackend/ttatta/service/DiaryService/DiaryQueryServiceImpl.java +++ b/src/main/java/TtattaBackend/ttatta/service/DiaryService/DiaryQueryServiceImpl.java @@ -6,12 +6,10 @@ import TtattaBackend.ttatta.aws.s3.AmazonS3Manager; import TtattaBackend.ttatta.config.security.SecurityUtil; import TtattaBackend.ttatta.converter.DiaryConverter; +import TtattaBackend.ttatta.converter.LocationLogConverter; import TtattaBackend.ttatta.domain.*; import TtattaBackend.ttatta.domain.enums.IsActive; -import TtattaBackend.ttatta.repository.DiaryCategoryRepository; -import TtattaBackend.ttatta.repository.DiaryRepository; -import TtattaBackend.ttatta.repository.MemoryDiaryAlarmRepository; -import TtattaBackend.ttatta.repository.UserRepository; +import TtattaBackend.ttatta.repository.*; import TtattaBackend.ttatta.service.AlarmService.AlarmCommandService; import TtattaBackend.ttatta.security.DecryptedLocation; import TtattaBackend.ttatta.security.EnvelopeCryptoService; @@ -34,8 +32,6 @@ import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.Point; -import static TtattaBackend.ttatta.apiPayload.code.status.ErrorStatus.USER_NOT_FOUND; - @Slf4j @Service @RequiredArgsConstructor @@ -47,6 +43,7 @@ public class DiaryQueryServiceImpl implements DiaryQueryService{ private final AmazonS3Manager s3Manager; private final AlarmCommandService alarmCommandService; private final MemoryDiaryAlarmRepository memoryDiaryAlarmRepository; + private final LocationLogRepository locationLogRepository; static final double M_PER_DEG_LAT = 111_320.0; // 검색 범위 설정 private static final int SEARCH_RANGE = 100; // 검색 범위 설정 (100m) @@ -222,7 +219,7 @@ public DiaryResponseDTO.MapResultDTO getMapDiaryList(Long clusterId, Long diaryC String objectKey = photoList.get(0).getImageUrl(); presignedUrl = s3Manager.generatePresignedUrlForView(objectKey); } - + saveLocationLog(user, "지도 일기 페이징 조회 서비스", user.getId().toString()); return DiaryConverter.toMapDiaryDTO(diariesPage, presignedUrl); } @@ -272,7 +269,7 @@ public List getDiaryDateList() { public void findRemindDiary(DiaryRequestDTO.RemindDTO request) { Long userId = SecurityUtil.getCurrentUserId(); Users user = userRepository.findById(userId).orElseThrow( - () -> new ExceptionHandler(USER_NOT_FOUND)); + () -> new ExceptionHandler(ErrorStatus.USER_NOT_FOUND)); // 위치 기반 추억 회상 알림이 꺼져있는 경우 MemoryDiaryAlarm memoryDiaryAlarm = memoryDiaryAlarmRepository.findByUsers(user) @@ -329,6 +326,9 @@ public void findRemindDiary(DiaryRequestDTO.RemindDTO request) { // 알림 보내기 alarmCommandService.sendMemoryDiaryAlarm(user, timeMessage, nearestDiary.get().getId()); + + // 위치 정보 이용 로그 저장 + locationLogRepository.save(LocationLogConverter.toLocationsLogs(user, "위치기반리마인드 알림 서비스", user.getId().toString())); } @Override @@ -380,6 +380,10 @@ public List getMapDiaryList(DiaryRequestDTO.ViewOnMapDTO request) { return viewOnMapDiaries; } + private void saveLocationLog(Users user, String provisionalService, String Receipient) { + locationLogRepository.save(LocationLogConverter.toLocationsLogs(user, provisionalService, Receipient)); + } + // 화면상의 네 꼭지점 받아옴 private Polygon buildPolygonFromUserScreen(DiaryRequestDTO.ViewOnMapDTO req) { // 네 꼭지점이 시계/반시계 순서로 들어온다고 가정. (lng, lat) 순서 유의 diff --git a/src/main/java/TtattaBackend/ttatta/service/SuperAdminPageService/SuperAdminPageQueryService.java b/src/main/java/TtattaBackend/ttatta/service/SuperAdminPageService/SuperAdminPageQueryService.java new file mode 100644 index 00000000..81e2235d --- /dev/null +++ b/src/main/java/TtattaBackend/ttatta/service/SuperAdminPageService/SuperAdminPageQueryService.java @@ -0,0 +1,10 @@ +package TtattaBackend.ttatta.service.SuperAdminPageService; + +import TtattaBackend.ttatta.domain.LocationAccessLogs; +import org.springframework.data.domain.Page; + +import java.time.LocalDate; + +public interface SuperAdminPageQueryService { + Page search(int page, int size, String keyword, LocalDate fromDate, LocalDate toDate); +} diff --git a/src/main/java/TtattaBackend/ttatta/service/SuperAdminPageService/SuperAdminPageQueryServiceImpl.java b/src/main/java/TtattaBackend/ttatta/service/SuperAdminPageService/SuperAdminPageQueryServiceImpl.java new file mode 100644 index 00000000..a44b46da --- /dev/null +++ b/src/main/java/TtattaBackend/ttatta/service/SuperAdminPageService/SuperAdminPageQueryServiceImpl.java @@ -0,0 +1,37 @@ +package TtattaBackend.ttatta.service.SuperAdminPageService; + +import TtattaBackend.ttatta.domain.LocationAccessLogs; +import TtattaBackend.ttatta.repository.LocationAccessLogRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Service +@RequiredArgsConstructor +public class SuperAdminPageQueryServiceImpl implements SuperAdminPageQueryService{ + + private final LocationAccessLogRepository locationAccessLogRepository; + + @Override + public Page search(int page, int size, String keyword, LocalDate fromDate, LocalDate toDate) { + Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "updatedAt")); + + LocalDateTime safeMin = LocalDateTime.of(1000, 1, 1, 0, 0); + LocalDateTime safeMax = LocalDateTime.of(9999, 12, 31, 23, 59, 59); + + LocalDateTime from = (fromDate != null) ? fromDate.atStartOfDay() : safeMin; + LocalDateTime toEx = (toDate != null) ? toDate.plusDays(1).atStartOfDay() : safeMax; + + String kw = (keyword != null && !keyword.isBlank()) ? keyword.trim() : ""; + + return locationAccessLogRepository.searchWithKeywordAndDate( + pageable, from, toEx, kw + ); + } +} diff --git a/src/main/java/TtattaBackend/ttatta/service/UserService/UserCommandServiceImpl.java b/src/main/java/TtattaBackend/ttatta/service/UserService/UserCommandServiceImpl.java index 6a457e55..21a0509d 100644 --- a/src/main/java/TtattaBackend/ttatta/service/UserService/UserCommandServiceImpl.java +++ b/src/main/java/TtattaBackend/ttatta/service/UserService/UserCommandServiceImpl.java @@ -41,6 +41,7 @@ import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; @@ -106,7 +107,7 @@ public Users signUp(UserRequestDTO.SignUpRequestDTO request) { if (usernameAvailable.equals(IsAvailable.UNAVAILABLE)) { throw new ExceptionHandler(ErrorStatus.USERNAME_ALREADY_EXIST); } - + // 사용자 생성 Users newUser = UserConverter.toUsers(request); newUser.encodePassword(passwordEncoder.encode(request.getPassword())); // 일상 카테고리 생성 @@ -151,7 +152,7 @@ public UserResponseDTO.UserKaKaoOpenIdResultDTO openIdKakao(String openId) { if (userSub.isPresent()) { Users ExistUser = userSub.get(); String key = "users:" + ExistUser.getId().toString(); - String accessToken = generateAccessToken(userSub.get().getId(), accessExpTime); + String accessToken = generateAccessToken(userSub.get().getId(), userSub.get().getRole(), accessExpTime); String refreshToken = generateAndSaveRefreshToken(key, refreshExpTime); Boolean isRegistered = ExistUser.getStatus().equals(UserStatus.PENDING) ? false : true; @@ -167,7 +168,7 @@ public UserResponseDTO.UserKaKaoOpenIdResultDTO openIdKakao(String openId) { // 액세스 토큰 및 리프레시 토큰 생성 String key = "users:" + savedUser.getId().toString(); - String accessToken = generateAccessToken(savedUser.getId(), accessExpTime); + String accessToken = generateAccessToken(savedUser.getId(), savedUser.getRole(), accessExpTime); String refreshToken = generateAndSaveRefreshToken(key, refreshExpTime); return UserConverter.toUserKaKaoOpenIdResultDTO(false, accessToken, refreshToken, savedUser); } @@ -192,7 +193,7 @@ public UserResponseDTO.KaKaoFinalSignUpResultDTO kakaoSignUp(UserRequestDTO.Sign // 액세스 토큰 및 리프레시 토큰 생성 String key = "users:" + savedUser.getId().toString(); - String accessToken = generateAccessToken(savedUser.getId(), accessExpTime); + String accessToken = generateAccessToken(savedUser.getId(), savedUser.getRole(), accessExpTime); String refreshToken = generateAndSaveRefreshToken(key, refreshExpTime); return UserConverter.toUserKaKaoFinalSignUpResultDTO(accessToken, refreshToken, savedUser); } @@ -217,7 +218,7 @@ public UserResponseDTO.UserSignInResultDTO signIn(UserRequestDTO.SignInRequestDT Users getUser = userRepository.findByUsername(authentication.getName()).orElseThrow(() -> new ExceptionHandler(USER_NOT_FOUND)); String key = "users:" + getUser.getId().toString(); - String accessToken = generateAccessToken(getUser.getId(), accessExpTime); + String accessToken = generateAccessToken(getUser.getId(), getUser.getRole(), accessExpTime); String refreshToken = generateAndSaveRefreshToken(key, refreshExpTime); return UserConverter.toUserSignInResultDTO(getUser, accessToken, refreshToken); @@ -226,6 +227,7 @@ public UserResponseDTO.UserSignInResultDTO signIn(UserRequestDTO.SignInRequestDT @Override public UserResponseDTO.RefreshResultDTO refresh(String refreshToken) { Long userId = SecurityUtil.getCurrentUserId(); + UserRole role = SecurityUtil.getCurrentUserRole(); String key = "users:" + userId.toString(); String accessToken; String newRefreshToken; @@ -235,7 +237,7 @@ public UserResponseDTO.RefreshResultDTO refresh(String refreshToken) { System.out.println("userId: " + userId); System.out.println("redis에서 가져온 refreshToken: " + getRefreshTokenFromRedis); if (refreshToken.equals(getRefreshTokenFromRedis)) { - accessToken = generateAccessToken(userId, accessExpTime); + accessToken = generateAccessToken(userId, role, accessExpTime); newRefreshToken = generateAndSaveRefreshToken(key, refreshExpTime); } else { throw new ExceptionHandler(REFRESHTOKEN_NOT_EQUAL); @@ -481,11 +483,11 @@ public Long getUserPoint() { return user.getPoint(); } - private String generateAccessToken(Long userId, int accessExpTime) { + private String generateAccessToken(Long userId, UserRole role, int accessExpTime) { // 인증 완료 후 jwt토큰(accessToken) 생성 - Map valueMap = Map.of( - "userId", userId // String으로 저장??? 그래서 SecurityUtil에서 Long으로 타입변환 해주나? - ); + Map valueMap = new HashMap<>(); + valueMap.put("userId", userId); // String으로 저장??? 그래서 SecurityUtil에서 Long으로 타입변환 해주나? + valueMap.put("role", role); return jwtUtils.generateToken(valueMap, accessExpTime); } diff --git a/src/main/java/TtattaBackend/ttatta/web/controller/AdminPageController.java b/src/main/java/TtattaBackend/ttatta/web/controller/AdminPageController.java new file mode 100644 index 00000000..1653c690 --- /dev/null +++ b/src/main/java/TtattaBackend/ttatta/web/controller/AdminPageController.java @@ -0,0 +1,49 @@ +package TtattaBackend.ttatta.web.controller; + +import TtattaBackend.ttatta.repository.LocationLogRepository; +import TtattaBackend.ttatta.service.AdminPageService.AdminPageQueryService; +import lombok.RequiredArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.time.LocalDate; + +@Controller +@RequiredArgsConstructor +public class AdminPageController { + + private final AdminPageQueryService adminPageQueryService; + private final LocationLogRepository locationLogRepository; + +// @GetMapping("/admin/location-log") +// public String adminLocationLogPage(Model model) { +// List locationLogList = locationLogRepository.findAllByOrderByCreatedAtDesc(); +// model.addAttribute("logs", locationLogList); // 템플릿에서 ${logs}로 사용 +// return "adminPages/location-log"; // templates/adminPages/location-log.html 렌더링 +// } + + @GetMapping("/admin/location-log") + public String list(@RequestParam(defaultValue = "0") int page, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) @DateTimeFormat(pattern="yyyy-MM-dd") LocalDate fromDate, + @RequestParam(required = false) @DateTimeFormat(pattern="yyyy-MM-dd") LocalDate toDate, + Model model) { + int pageSize = 10; + + // 1) 조회 + var resultPage = adminPageQueryService.search(page, pageSize, keyword, fromDate, toDate); + + // 2) 모델에 "다시" 넣기 (템플릿에서 th:value, th:href에 사용) + model.addAttribute("logs", resultPage.getContent()); + model.addAttribute("totalPage", resultPage.getTotalPages()); + model.addAttribute("page", page); + model.addAttribute("keyword", keyword); + model.addAttribute("fromDate", fromDate); + model.addAttribute("toDate", toDate); + + return "adminPages/location-log"; + } +} diff --git a/src/main/java/TtattaBackend/ttatta/web/controller/LoginController.java b/src/main/java/TtattaBackend/ttatta/web/controller/LoginController.java new file mode 100644 index 00000000..96f21a44 --- /dev/null +++ b/src/main/java/TtattaBackend/ttatta/web/controller/LoginController.java @@ -0,0 +1,13 @@ +package TtattaBackend.ttatta.web.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class LoginController { + + @GetMapping("/admin/login") + public String loginPage() { + return "loginForm"; // templates/loginForm.html 렌더링 + } +} diff --git a/src/main/java/TtattaBackend/ttatta/web/controller/SuperAdminPageController.java b/src/main/java/TtattaBackend/ttatta/web/controller/SuperAdminPageController.java new file mode 100644 index 00000000..8a059edb --- /dev/null +++ b/src/main/java/TtattaBackend/ttatta/web/controller/SuperAdminPageController.java @@ -0,0 +1,52 @@ +package TtattaBackend.ttatta.web.controller; + +import TtattaBackend.ttatta.domain.LocationAccessLogs; +import TtattaBackend.ttatta.service.SuperAdminPageService.SuperAdminPageQueryService; +import lombok.RequiredArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.time.LocalDate; + +@Controller +@RequiredArgsConstructor +public class SuperAdminPageController { + + private final SuperAdminPageQueryService superAdminPageQueryService; + + @GetMapping("/super/admin/home") + public String superAdminHomePage() { + return "superAdminPages/home"; + } + + @GetMapping("/super/admin/admin-log") + public String superAdminAdminLogPage(@RequestParam(defaultValue = "0") int page, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) @DateTimeFormat(pattern="yyyy-MM-dd") LocalDate fromDate, + @RequestParam(required = false) @DateTimeFormat(pattern="yyyy-MM-dd") LocalDate toDate, + Model model) { + int pageSize = 10; + System.out.println("page: " + page); + + // 1) 조회 + var resultPage = superAdminPageQueryService.search(page, pageSize, keyword, fromDate, toDate); + + System.out.println(resultPage.getContent()); + for (LocationAccessLogs log : resultPage.getContent()) { + System.out.println("Log ID: " + log.getId()); + } + + // 2) 모델에 "다시" 넣기 (템플릿에서 th:value, th:href에 사용) + model.addAttribute("logs", resultPage.getContent()); + model.addAttribute("totalPage", resultPage.getTotalPages()); + model.addAttribute("page", page); + model.addAttribute("keyword", keyword); + model.addAttribute("fromDate", fromDate); + model.addAttribute("toDate", toDate); + + return "superAdminPages/admin-log"; + } +} diff --git a/src/main/java/TtattaBackend/ttatta/web/controller/UserController.java b/src/main/java/TtattaBackend/ttatta/web/controller/UserController.java index 751fdc6e..f594b5aa 100644 --- a/src/main/java/TtattaBackend/ttatta/web/controller/UserController.java +++ b/src/main/java/TtattaBackend/ttatta/web/controller/UserController.java @@ -313,5 +313,4 @@ public ApiResponse deletePin() { userCommandService.deletePin(); return ApiResponse.onSuccess(""); } - } diff --git a/src/main/java/TtattaBackend/ttatta/web/dto/AdminResponseDto.java b/src/main/java/TtattaBackend/ttatta/web/dto/AdminResponseDto.java new file mode 100644 index 00000000..f2edad63 --- /dev/null +++ b/src/main/java/TtattaBackend/ttatta/web/dto/AdminResponseDto.java @@ -0,0 +1,23 @@ +package TtattaBackend.ttatta.web.dto; + +import TtattaBackend.ttatta.domain.enums.LoginType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +public class AdminResponseDto { + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class LocationLogResponseDto { + private String target; // 대상 + private String acquisitionPath; // 취득 경로 + private String provisionalService; // 제공 서비스 + private String Recipient; // 제공받는 자 + private LocalDateTime createdAt; // 이용일시 + } +} diff --git a/src/main/java/TtattaBackend/ttatta/web/dto/SuperAdminResponseDto.java b/src/main/java/TtattaBackend/ttatta/web/dto/SuperAdminResponseDto.java new file mode 100644 index 00000000..3de589df --- /dev/null +++ b/src/main/java/TtattaBackend/ttatta/web/dto/SuperAdminResponseDto.java @@ -0,0 +1,21 @@ +package TtattaBackend.ttatta.web.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +public class SuperAdminResponseDto { + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class AdminLogResponseDto { + private String handler; // 취급자 + private String requester; // 요청자 + private String purpose; // 목적 + private LocalDateTime createdAt; // 일시 + } +} diff --git a/src/main/resources/js/location_log.js b/src/main/resources/js/location_log.js new file mode 100644 index 00000000..e1a5c8ae --- /dev/null +++ b/src/main/resources/js/location_log.js @@ -0,0 +1,9 @@ +let currPage = 0; +let keyword; +let startDate; +let endDate; +let removeList = []; + +window.addEventListener("load", function () { + +}); diff --git a/src/main/resources/templates/adminPages/location-log.html b/src/main/resources/templates/adminPages/location-log.html new file mode 100644 index 00000000..1aabe81a --- /dev/null +++ b/src/main/resources/templates/adminPages/location-log.html @@ -0,0 +1,184 @@ + + + + + + 위치정보 이용,제공사실 확인자료 + + + + + +
+
위치정보 이용,제공사실 확인자료
+ +
+ +
+
+ + + ~ + + + + +
+ + +
+ +
+
+ + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + #대상취득경로제공 서비스제공받는 자이용일시비고
+ + 1회원ID/디바이스 등GPS/기지국/Wi-Fi근처 일기 알림2025-09-21 20:12:03 + + +
표시할 기록이 없습니다.
+
+
+
+ + +
+ +
+
+ + + + diff --git a/src/main/resources/templates/loginForm.html b/src/main/resources/templates/loginForm.html new file mode 100644 index 00000000..e5655a3c --- /dev/null +++ b/src/main/resources/templates/loginForm.html @@ -0,0 +1,109 @@ + + + + + + 관리자 LOGIN + + + +
+ +
+
[ 위치정보조회 ] 따따 관리자 로그인
+
+
+ + + +
+ +
+ + +
+ + + + +
+ +
+ + +
이메일/비밀번호가 올바르지 않습니다.
+
정상적으로 로그아웃되었습니다.
+
+
+
+ + \ No newline at end of file diff --git a/src/main/resources/templates/logout-header.html b/src/main/resources/templates/logout-header.html new file mode 100644 index 00000000..566549bd --- /dev/null +++ b/src/main/resources/templates/logout-header.html @@ -0,0 +1,10 @@ + + + + + Title + + + + + \ No newline at end of file diff --git a/src/main/resources/templates/superAdminPages/admin-log.html b/src/main/resources/templates/superAdminPages/admin-log.html new file mode 100644 index 00000000..90dbfbeb --- /dev/null +++ b/src/main/resources/templates/superAdminPages/admin-log.html @@ -0,0 +1,123 @@ + + + + + 접근 로그 조회 + + + + +
+
접근 로그 조회
+ +
+ +
+
+ + + ~ + + + +
+ +
+ +
+
+ + +
+ + + + + +
+ + + + + + + + + + + + + + + + + + +
접근권한자 식별 정보접속일시
admin012025-10-01 12:00:00
표시할 기록이 없습니다.
+
+
+
+ + +
+ +
+
+ + + + diff --git a/src/main/resources/templates/superAdminPages/home.html b/src/main/resources/templates/superAdminPages/home.html new file mode 100644 index 00000000..801bd617 --- /dev/null +++ b/src/main/resources/templates/superAdminPages/home.html @@ -0,0 +1,44 @@ + + + + + 로그 조회 선택 + + + + +

로그 조회 선택

+ +관리자 접속 로그 조회 +위치정보 로그 조회 + + +