Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config
2 changes: 2 additions & 0 deletions src/main/java/com/meetup/server/ServerApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication(exclude = { RateLimiterAutoConfiguration.class })
@EnableCaching
@EnableScheduling
@EnableAsync
public class ServerApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@

import com.meetup.server.admin.dto.request.AdminRegisterRequest;
import com.meetup.server.admin.dto.response.AdminEventResponse;
import com.meetup.server.admin.dto.response.DailyStatsResponse;
import com.meetup.server.admin.dto.response.AdminUserResponse;
import com.meetup.server.admin.dto.response.DailyEventStatsResponse;
import com.meetup.server.admin.dto.response.DailyUserStatsResponse;
import com.meetup.server.admin.implement.AdminValidator;
import com.meetup.server.admin.implement.AdminWriter;
import com.meetup.server.event.domain.Event;
import com.meetup.server.event.implement.EventReader;
import com.meetup.server.startpoint.implement.StartPointReader;
import com.meetup.server.startpoint.infrastructure.querydsl.projection.ParticipantCount;
import com.meetup.server.user.domain.User;
import com.meetup.server.user.implement.LogUserLoginReader;
import com.meetup.server.user.implement.UserReader;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
Expand All @@ -31,6 +36,8 @@ public class AdminService {
private final AdminWriter adminWriter;
private final EventReader eventReader;
private final StartPointReader startPointReader;
private final LogUserLoginReader logUserLoginReader;
private final UserReader userReader;

@Transactional
public void register(AdminRegisterRequest request) {
Expand Down Expand Up @@ -67,11 +74,33 @@ public Page<AdminEventResponse> getAllEvents(Pageable pageable) {
);
}

public DailyStatsResponse getDailyStats() {
public Page<AdminUserResponse> getAllUsers(Pageable pageable) {
Page<User> users = userReader.readAll(pageable);

List<AdminUserResponse> adminUserResponses = users.getContent().stream()
.map(AdminUserResponse::from)
.toList();

return new PageImpl<>(
adminUserResponses,
pageable,
users.getTotalElements()
);
}

public DailyEventStatsResponse getDailyEventStats() {
LocalDate todayDate = LocalDate.now();
long dailyEventCount = eventReader.readDailyEventCount(todayDate);
long dailyParticipantCount = startPointReader.readDailyParticipantCount(todayDate);

return DailyStatsResponse.of(dailyEventCount, dailyParticipantCount);
return DailyEventStatsResponse.of(dailyEventCount, dailyParticipantCount);
}

public DailyUserStatsResponse getDailyUserStats() {
LocalDate todayDate = LocalDate.now();
long dailyLoginUserCount = logUserLoginReader.readDailyLoginUserCount(todayDate);
long dailyRegisterUserCount = userReader.readDailyRegisterUserCount(todayDate);

return DailyUserStatsResponse.of(dailyLoginUserCount, dailyRegisterUserCount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.meetup.server.admin.dto.response;

import com.meetup.server.user.domain.User;

import java.time.LocalDateTime;

public record AdminUserResponse(
Long userId,
String nickname,
String email,
boolean isPersonalInfoAgreement,
boolean isMarketingAgreement,
LocalDateTime createdDateTime
) {
public static AdminUserResponse from(User user) {
return new AdminUserResponse(
user.getUserId(),
user.getNickname(),
user.getEmail(),
user.isPersonalInfoAgreement(),
user.isMarketingAgreement(),
user.getCreatedAt()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.meetup.server.admin.dto.response;

public record DailyEventStatsResponse(
long dailyEventCount,
long dailyParticipantCount
) {
public static DailyEventStatsResponse of(long eventCount, long participantCount) {
return new DailyEventStatsResponse(eventCount, participantCount);
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.meetup.server.admin.dto.response;

public record DailyUserStatsResponse(
long dailyLoginUserCount,
long dailyRegisterUserCount
) {
public static DailyUserStatsResponse of(long loginCount, long registerCount) {
return new DailyUserStatsResponse(loginCount, registerCount);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import com.meetup.server.admin.dto.request.AdminLoginRequest;
import com.meetup.server.admin.dto.request.AdminRegisterRequest;
import com.meetup.server.admin.dto.response.AdminEventResponse;
import com.meetup.server.admin.dto.response.DailyStatsResponse;
import com.meetup.server.admin.dto.response.AdminUserResponse;
import com.meetup.server.admin.dto.response.DailyEventStatsResponse;
import com.meetup.server.admin.dto.response.DailyUserStatsResponse;
import com.meetup.server.admin.exception.AdminErrorType;
import com.meetup.server.admin.exception.AdminException;
import jakarta.validation.Valid;
Expand Down Expand Up @@ -72,11 +74,24 @@ public String getAllEvents(
@PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
) {
Page<AdminEventResponse> eventPages = adminService.getAllEvents(pageable);
DailyEventStatsResponse dailyEventStats = adminService.getDailyEventStats();

model.addAttribute("eventPages", eventPages);
model.addAttribute("clientUrl", clientUrl);

DailyStatsResponse dailyStats = adminService.getDailyStats();
model.addAttribute("dailyStats", dailyStats);
model.addAttribute("dailyEventStats", dailyEventStats);
return "admin/events";
}

@GetMapping("/users")
public String getAllUsers(
Model model,
@PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
) {
Page<AdminUserResponse> userPages = adminService.getAllUsers(pageable);
DailyUserStatsResponse dailyUserStats = adminService.getDailyUserStats();

model.addAttribute("userPages", userPages);
model.addAttribute("dailyUserStats", dailyUserStats);
return "admin/users";
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.meetup.server.auth.support.handler;

import com.meetup.server.user.domain.type.LoginStatus;
import com.meetup.server.user.implement.LogUserLoginWriter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
Expand All @@ -17,6 +20,8 @@
@Component
public class OAuth2LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {

private final LogUserLoginWriter logUserLoginWriter;

@Value("${app.oauth2.failureRedirectUri}")
private String redirectUri;

Expand All @@ -27,6 +32,10 @@ public void onAuthenticationFailure(

log.info("OAuth2 login failed: {}", exception.getMessage());

String ipAddress = request.getHeader("X-Real-IP");
String userAgent = request.getHeader(HttpHeaders.USER_AGENT);
logUserLoginWriter.save(null, LoginStatus.FAILURE, ipAddress, userAgent, exception.getMessage());

String targetUrl = UriComponentsBuilder.fromUriString(redirectUri)
.queryParam("error", exception.getLocalizedMessage())
.build().toUriString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
import com.meetup.server.auth.dto.CustomOAuth2User;
import com.meetup.server.auth.support.CookieUtil;
import com.meetup.server.global.support.jwt.JwtTokenProvider;
import com.meetup.server.user.domain.type.LoginStatus;
import com.meetup.server.user.implement.LogUserLoginWriter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
Expand All @@ -22,6 +25,7 @@ public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHan

private final JwtTokenProvider tokenProvider;
private final CookieUtil cookieUtil;
private final LogUserLoginWriter logUserLoginWriter;

@Value("${app.oauth2.successRedirectUri}")
private String successRedirectUri;
Expand All @@ -33,6 +37,10 @@ public void onAuthenticationSuccess(

CustomOAuth2User oAuth2User = (CustomOAuth2User) authentication.getPrincipal();

String ipAddress = request.getHeader("X-Real-IP");
String userAgent = request.getHeader(HttpHeaders.USER_AGENT);
logUserLoginWriter.save(oAuth2User.getUserId(), LoginStatus.SUCCESS, ipAddress, userAgent, null);

String accessToken = tokenProvider.createAccessToken(oAuth2User);
String refreshToken = tokenProvider.createRefreshToken(oAuth2User);

Expand Down
47 changes: 47 additions & 0 deletions src/main/java/com/meetup/server/user/domain/LogUserLogin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.meetup.server.user.domain;

import com.meetup.server.global.domain.BaseEntity;
import com.meetup.server.global.util.StringUtil;
import com.meetup.server.user.domain.type.LoginStatus;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "log_user_login")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class LogUserLogin extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "log_user_login_id")
private Long id;

@Column(name = "user_id")
private Long userId;

@Enumerated(EnumType.STRING)
@Column(name = "login_status", nullable = false)
private LoginStatus loginStatus;

@Column(name = "ip_address", length = 45)
private String ipAddress;

@Column(name = "user_agent")
private String userAgent;

@Column(name = "fail_reason")
private String failReason;

@Builder
public LogUserLogin(Long userId, LoginStatus loginStatus, String ipAddress, String userAgent, String failReason) {
this.userId = userId;
this.loginStatus = loginStatus;
this.ipAddress = ipAddress;
this.userAgent = StringUtil.truncate(userAgent, 255);
this.failReason = StringUtil.truncate(failReason, 255);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.meetup.server.user.domain.type;

public enum LoginStatus {
SUCCESS,
FAILURE,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.meetup.server.user.implement;

import com.meetup.server.user.infrastructure.jpa.LogUserLoginRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

@Component
@RequiredArgsConstructor
public class LogUserLoginReader {

private final LogUserLoginRepository logUserLoginRepository;

public long readDailyLoginUserCount(LocalDate todayDate) {
LocalDateTime startDateTime = todayDate.atStartOfDay();
LocalDateTime endDateTime = todayDate.atTime(LocalTime.MAX);
return logUserLoginRepository.countUniqueLoginUsers(startDateTime, endDateTime);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.meetup.server.user.implement;

import com.meetup.server.user.domain.LogUserLogin;
import com.meetup.server.user.domain.type.LoginStatus;
import com.meetup.server.user.infrastructure.jpa.LogUserLoginRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.concurrent.CompletableFuture;

@Slf4j
@Component
@RequiredArgsConstructor
public class LogUserLoginWriter {

private final LogUserLoginRepository logUserLoginRepository;

@Async
public CompletableFuture<Void> save(Long userId, LoginStatus loginStatus, String ipAddress, String userAgent, String failReason) {
try {
logUserLoginRepository.save(
LogUserLogin.builder()
.userId(userId)
.loginStatus(loginStatus)
.ipAddress(ipAddress)
.userAgent(userAgent)
.failReason(failReason)
.build()
);
} catch (Exception e) {
log.error("[LogUserLoginWriter]: 유저 로그인 로그 저장에 실패했습니다. userId: {}", userId, e);
}
return CompletableFuture.completedFuture(null);
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/meetup/server/user/implement/UserReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@
import com.meetup.server.user.exception.UserException;
import com.meetup.server.user.infrastructure.jpa.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Optional;

@Component
Expand All @@ -24,4 +29,14 @@ public User read(Long userId) {
return readUserIfExists(userId)
.orElseThrow(() -> new UserException(UserErrorType.USER_NOT_FOUND));
}

public Page<User> readAll(Pageable pageable) {
return userRepository.findAll(pageable);
}

public long readDailyRegisterUserCount(LocalDate todayDate) {
LocalDateTime startDateTime = todayDate.atStartOfDay();
LocalDateTime endDateTime = todayDate.atTime(LocalTime.MAX);
return userRepository.countByCreatedAtBetween(startDateTime, endDateTime);
}
}
Loading