diff --git a/src/main/java/com/example/sinitto/SinittoApplication.java b/src/main/java/com/example/sinitto/SinittoApplication.java index 7aef98e4..a8243d84 100644 --- a/src/main/java/com/example/sinitto/SinittoApplication.java +++ b/src/main/java/com/example/sinitto/SinittoApplication.java @@ -1,5 +1,6 @@ package com.example.sinitto; +import com.example.sinitto.common.properties.AdminProperties; import com.example.sinitto.common.properties.DummyProperties; import com.example.sinitto.common.properties.KakaoProperties; import org.springframework.boot.SpringApplication; @@ -10,7 +11,7 @@ @SpringBootApplication @EnableJpaAuditing -@EnableConfigurationProperties({KakaoProperties.class, DummyProperties.class}) +@EnableConfigurationProperties({KakaoProperties.class, DummyProperties.class, AdminProperties.class}) @EnableScheduling public class SinittoApplication { diff --git a/src/main/java/com/example/sinitto/auth/controller/AuthController.java b/src/main/java/com/example/sinitto/auth/controller/AuthController.java index fb11918d..ba9750c6 100644 --- a/src/main/java/com/example/sinitto/auth/controller/AuthController.java +++ b/src/main/java/com/example/sinitto/auth/controller/AuthController.java @@ -58,7 +58,7 @@ public ResponseEntity kakaoCallback(@RequestParam("code") String @Operation(summary = "Redis안의 모든 데이터 제거", description = "발급된 refreshToken을 사용하지 못하게 Redis 안의 모든 데이터를 제거합니다.") @DeleteMapping("/redis") - public ResponseEntity deleteAllDataFromRedis(){ + public ResponseEntity deleteAllDataFromRedis() { tokenService.deleteAllDataFromRedis(); return new ResponseEntity<>(HttpStatus.OK); } diff --git a/src/main/java/com/example/sinitto/auth/service/TokenService.java b/src/main/java/com/example/sinitto/auth/service/TokenService.java index fa972d3d..713708ee 100644 --- a/src/main/java/com/example/sinitto/auth/service/TokenService.java +++ b/src/main/java/com/example/sinitto/auth/service/TokenService.java @@ -120,7 +120,7 @@ public TokenResponse refreshAccessToken(String refreshToken) { return new TokenResponse(newAccessToken, newRefreshToken); } - public void deleteAllDataFromRedis(){ + public void deleteAllDataFromRedis() { redisTemplate.getConnectionFactory() .getConnection() .serverCommands() diff --git a/src/main/java/com/example/sinitto/common/config/SwaggerConfig.java b/src/main/java/com/example/sinitto/common/config/SwaggerConfig.java index 9e7a9e76..6e8001c5 100644 --- a/src/main/java/com/example/sinitto/common/config/SwaggerConfig.java +++ b/src/main/java/com/example/sinitto/common/config/SwaggerConfig.java @@ -3,9 +3,9 @@ import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Info; -import io.swagger.v3.oas.models.servers.Server; import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.Environment; @@ -14,10 +14,9 @@ @Configuration public class SwaggerConfig { - private final Environment environment; - private static final String LOCAL_SERVER_URL = "http://localhost:8080"; private static final String PROD_SERVER_URL = "https://sinitto.site"; + private final Environment environment; public SwaggerConfig(Environment environment) { this.environment = environment; diff --git a/src/main/java/com/example/sinitto/common/config/WebConfig.java b/src/main/java/com/example/sinitto/common/config/WebConfig.java index ae986fd1..75558393 100644 --- a/src/main/java/com/example/sinitto/common/config/WebConfig.java +++ b/src/main/java/com/example/sinitto/common/config/WebConfig.java @@ -27,7 +27,7 @@ public class WebConfig implements WebMvcConfigurer { private static final int CONNECTIONS_PER_IP_PORT_PAIR = 5; private final JwtInterceptor jwtInterceptor; - public WebConfig(JwtInterceptor jwtInterceptor){ + public WebConfig(JwtInterceptor jwtInterceptor) { this.jwtInterceptor = jwtInterceptor; } diff --git a/src/main/java/com/example/sinitto/common/interceptor/JwtInterceptor.java b/src/main/java/com/example/sinitto/common/interceptor/JwtInterceptor.java index 10b9b13c..9e5ea039 100644 --- a/src/main/java/com/example/sinitto/common/interceptor/JwtInterceptor.java +++ b/src/main/java/com/example/sinitto/common/interceptor/JwtInterceptor.java @@ -14,11 +14,12 @@ public class JwtInterceptor implements HandlerInterceptor { private final MemberTokenService memberTokenService; - public JwtInterceptor(MemberTokenService memberTokenService){ + public JwtInterceptor(MemberTokenService memberTokenService) { this.memberTokenService = memberTokenService; } + @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){ + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); diff --git a/src/main/java/com/example/sinitto/common/properties/AdminProperties.java b/src/main/java/com/example/sinitto/common/properties/AdminProperties.java new file mode 100644 index 00000000..a7d6ee35 --- /dev/null +++ b/src/main/java/com/example/sinitto/common/properties/AdminProperties.java @@ -0,0 +1,10 @@ +package com.example.sinitto.common.properties; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +@ConfigurationProperties(prefix = "admin") +public record AdminProperties( + String adminEmail, + String adminPassword +) { +} \ No newline at end of file diff --git a/src/main/java/com/example/sinitto/common/service/SlackMessageService.java b/src/main/java/com/example/sinitto/common/service/SlackMessageService.java index 772040ed..59df7d10 100644 --- a/src/main/java/com/example/sinitto/common/service/SlackMessageService.java +++ b/src/main/java/com/example/sinitto/common/service/SlackMessageService.java @@ -14,17 +14,14 @@ @Service public class SlackMessageService { + private final RestTemplate restTemplate; @Value("${slack.notice.webhook.url}") private String slackNoticeWebhookUrl; - @Value("${slack.charge.request.url}") private String chargeRequestUrl; - @Value("${slack.withdraw.request.url}") private String withdrawRequestUrl; - private final RestTemplate restTemplate; - public SlackMessageService(RestTemplate restTemplate) { this.restTemplate = restTemplate; } diff --git a/src/main/java/com/example/sinitto/guard/service/GuardService.java b/src/main/java/com/example/sinitto/guard/service/GuardService.java index b08fab92..3d715644 100644 --- a/src/main/java/com/example/sinitto/guard/service/GuardService.java +++ b/src/main/java/com/example/sinitto/guard/service/GuardService.java @@ -59,7 +59,7 @@ public void createSenior(Long memberId, SeniorRequest seniorRequest) { ); if (member.isSinitto()) throw new BadRequestException("보호자만 이용할 수 있습니다."); - if(seniorRepository.existsByPhoneNumber(seniorRequest.seniorPhoneNumber())) { + if (seniorRepository.existsByPhoneNumber(seniorRequest.seniorPhoneNumber())) { throw new BadRequestException("이미 등록되어 있는 전화번호 입니다."); } @@ -90,7 +90,7 @@ public void updateSenior(Long memberId, Long seniorId, SeniorRequest seniorReque () -> new NotFoundException("이메일에 해당하는 시니어를 찾을 수 없습니다.") ); - if(!senior.getPhoneNumber().equals(seniorRequest.seniorPhoneNumber()) + if (!senior.getPhoneNumber().equals(seniorRequest.seniorPhoneNumber()) && seniorRepository.existsByPhoneNumber(seniorRequest.seniorPhoneNumber())) { throw new BadRequestException("이미 등록되어 있는 전화번호 입니다."); } diff --git a/src/main/java/com/example/sinitto/member/controller/MemberAdminController.java b/src/main/java/com/example/sinitto/member/controller/MemberAdminController.java index cd05991b..fd1bd10f 100644 --- a/src/main/java/com/example/sinitto/member/controller/MemberAdminController.java +++ b/src/main/java/com/example/sinitto/member/controller/MemberAdminController.java @@ -1,9 +1,11 @@ package com.example.sinitto.member.controller; import com.example.sinitto.auth.service.TokenService; +import com.example.sinitto.common.properties.AdminProperties; import com.example.sinitto.common.properties.DummyProperties; import com.example.sinitto.member.entity.Member; import com.example.sinitto.member.repository.MemberRepository; +import jakarta.servlet.http.HttpSession; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @@ -15,32 +17,61 @@ import java.util.List; @Controller -@RequestMapping("/dummy") +@RequestMapping public class MemberAdminController { private final MemberRepository memberRepository; private final TokenService tokenService; private final DummyProperties dummyProperties; - + private final AdminProperties adminProperties; private final List dummyEmails = Arrays.asList( "1chulsoo@example.com", "2kim@example.com", "3lee@example.com", "4park@example.com", "5choi@example.com", "6jeong@example.com", "7han@example.com", "8oh@example.com", "9lim@example.com", "10song@example.com" ); - public MemberAdminController(MemberRepository memberRepository, TokenService tokenService, DummyProperties dummyProperties) { + public MemberAdminController(MemberRepository memberRepository, TokenService tokenService, DummyProperties dummyProperties, AdminProperties adminProperties) { this.memberRepository = memberRepository; this.tokenService = tokenService; this.dummyProperties = dummyProperties; + this.adminProperties = adminProperties; } - @GetMapping + @GetMapping("/dummy") public String showDummyLoginPage(Model model) { List dummyMembers = memberRepository.findAllByEmailIn(dummyEmails); model.addAttribute("members", dummyMembers); return "dummy/login"; } - @PostMapping + @GetMapping("/admin/login") + public String showAdminLoginPage(HttpSession session) { + if (isAdmin(session)) { + return "redirect:/admin/point/charge"; + } + return "point/login"; + } + + @PostMapping("/admin/login") + public String login(@RequestParam String email, + @RequestParam String password, + HttpSession session) { + if (adminProperties.adminEmail().equals(email) && adminProperties.adminPassword().equals(password)) { + session.setAttribute("email", email); + session.setAttribute("role", "ADMIN"); + session.setMaxInactiveInterval(1800); + return "redirect:/admin/point/charge"; + } else { + return "redirect:/admin/login?error=true"; + } + } + + @PostMapping("/admin/logout") + public String logout(HttpSession session) { + session.invalidate(); + return "redirect:/admin/login"; + } + + @PostMapping("/dummy") public String login( @RequestParam("email") String email, @RequestParam("password") String password, @@ -67,4 +98,9 @@ public String login( String frontendRedirectUrl = env.equals("dev") ? dummyProperties.devRedirectUri() : dummyProperties.redirectUri(); return "redirect:" + frontendRedirectUrl + "?accessToken=" + accessToken + "&refreshToken=" + refreshToken + "&isSinitto=" + isSinitto; } + + private boolean isAdmin(HttpSession session) { + String role = (String) session.getAttribute("role"); + return "ADMIN".equals(role); + } } diff --git a/src/main/java/com/example/sinitto/member/service/MemberTokenService.java b/src/main/java/com/example/sinitto/member/service/MemberTokenService.java index fd18ef08..ad029861 100644 --- a/src/main/java/com/example/sinitto/member/service/MemberTokenService.java +++ b/src/main/java/com/example/sinitto/member/service/MemberTokenService.java @@ -11,7 +11,7 @@ public class MemberTokenService { private final TokenService tokenService; private final MemberRepository memberRepository; - public MemberTokenService(TokenService tokenService, MemberRepository memberRepository){ + public MemberTokenService(TokenService tokenService, MemberRepository memberRepository) { this.tokenService = tokenService; this.memberRepository = memberRepository; } diff --git a/src/main/java/com/example/sinitto/point/controller/PointAdminController.java b/src/main/java/com/example/sinitto/point/controller/PointAdminController.java index 50be1ec5..07ad9b89 100644 --- a/src/main/java/com/example/sinitto/point/controller/PointAdminController.java +++ b/src/main/java/com/example/sinitto/point/controller/PointAdminController.java @@ -3,6 +3,7 @@ import com.example.sinitto.point.dto.PointLogWithBankInfo; import com.example.sinitto.point.dto.PointLogWithDepositMessage; import com.example.sinitto.point.service.PointAdminService; +import jakarta.servlet.http.HttpSession; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; @@ -21,8 +22,10 @@ public PointAdminController(PointAdminService pointAdminService) { } @GetMapping("/admin/point/charge") - public String showAllChargeRequest(Model model) { - + public String showAllChargeRequest(Model model, HttpSession session) { + if (!isAdmin(session)) { + return "redirect:/admin/login"; + } List logWithDepositMessages = pointAdminService.getPointLogWithDepositMessage(); model.addAttribute("logWithDepositMessages", logWithDepositMessages); @@ -31,28 +34,37 @@ public String showAllChargeRequest(Model model) { } @PostMapping("/admin/point/charge/waiting/{pointLogId}") - public String changeToWaiting(@PathVariable Long pointLogId) { - + public String changeToWaiting(@PathVariable Long pointLogId, HttpSession session) { + if (!isAdmin(session)) { + return "redirect:/admin/login"; + } pointAdminService.changeChargeLogToWaiting(pointLogId); return "redirect:/admin/point/charge"; } @PostMapping("/admin/point/charge/complete/{pointLogId}") - public String changeToCompleteAndEarn(@PathVariable Long pointLogId) { - + public String changeToCompleteAndEarn(@PathVariable Long pointLogId, HttpSession session) { + if (!isAdmin(session)) { + return "redirect:/admin/login"; + } pointAdminService.earnPointAndChangeToChargeComplete(pointLogId); return "redirect:/admin/point/charge"; } @PostMapping("/admin/point/charge/fail/{pointLogId}") - public String changeToFail(@PathVariable Long pointLogId) { - + public String changeToFail(@PathVariable Long pointLogId, HttpSession session) { + if (!isAdmin(session)) { + return "redirect:/admin/login"; + } pointAdminService.changeChargeLogToFail(pointLogId); return "redirect:/admin/point/charge"; } @GetMapping("/admin/point/withdraw") - public String showAllWithdrawRequest(Model model) { + public String showAllWithdrawRequest(Model model, HttpSession session) { + if (!isAdmin(session)) { + return "redirect:/admin/login"; + } List logWithBankInfos = pointAdminService.getPointLogWithBankInfo(); @@ -62,24 +74,35 @@ public String showAllWithdrawRequest(Model model) { } @PostMapping("/admin/point/withdraw/waiting/{pointLogId}") - public String changeWithdrawLogToWaiting(@PathVariable Long pointLogId) { - + public String changeWithdrawLogToWaiting(@PathVariable Long pointLogId, HttpSession session) { + if (!isAdmin(session)) { + return "redirect:/admin/login"; + } pointAdminService.changeWithdrawLogToWaiting(pointLogId); return "redirect:/admin/point/withdraw"; } @PostMapping("/admin/point/withdraw/complete/{pointLogId}") - public String changeWithdrawLogToCompleteAndEarn(@PathVariable Long pointLogId) { - + public String changeWithdrawLogToCompleteAndEarn(@PathVariable Long pointLogId, HttpSession session) { + if (!isAdmin(session)) { + return "redirect:/admin/login"; + } pointAdminService.changeWithdrawLogToComplete(pointLogId); return "redirect:/admin/point/withdraw"; } @PostMapping("/admin/point/withdraw/fail/{pointLogId}") - public String changeWithdrawLogToFail(@PathVariable Long pointLogId) { - + public String changeWithdrawLogToFail(@PathVariable Long pointLogId, HttpSession session) { + if (!isAdmin(session)) { + return "redirect:/admin/login"; + } pointAdminService.changeWithdrawLogToFail(pointLogId); return "redirect:/admin/point/withdraw"; } + private boolean isAdmin(HttpSession session) { + String role = (String) session.getAttribute("role"); + return "ADMIN".equals(role); + } + } diff --git a/src/main/java/com/example/sinitto/review/entity/Review.java b/src/main/java/com/example/sinitto/review/entity/Review.java index 1c1f69fb..244e42f4 100644 --- a/src/main/java/com/example/sinitto/review/entity/Review.java +++ b/src/main/java/com/example/sinitto/review/entity/Review.java @@ -14,24 +14,18 @@ @Entity @EntityListeners(AuditingEntityListener.class) public class Review { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - @NotNull int starCountForRequest; - @NotNull int starCountForService; - @NotNull int starCountForSatisfaction; - @CreatedDate LocalDate postDate; - String content; - + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") @NotNull diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f0993146..f89fd298 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -7,3 +7,4 @@ spring.profiles.active=dev spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect spring.h2.console.enabled=false +server.servlet.session.tracking-modes = cookie diff --git a/src/main/resources/static/css/header.css b/src/main/resources/static/css/header.css new file mode 100644 index 00000000..e7da7b67 --- /dev/null +++ b/src/main/resources/static/css/header.css @@ -0,0 +1,54 @@ + +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 20px; + color: #333; +} + +.header { + background-color: transparent; + border: none; + padding: 20px; + color: #333; + margin-bottom: 30px; + display: flex; + justify-content: space-between; + align-items: center; + +.header h1 { + margin: 0; + font-size: 24px; + font-weight: 600; +} + + +nav { + display: flex; + gap: 15px; + justify-content: flex-end; +} + +button { + background-color: rgba(52, 87, 217, 0.8); + color: #ffffff; + border: none; + padding: 10px 20px; + font-size: 14px; + cursor: pointer; + border-radius: 4px; + transition: background-color 0.3s, transform 0.2s; +} + +button:hover { + background-color: rgba(52, 87, 217, 0.93); + transform: scale(1.03); +} + +button:active { + background-color: #00473c; +} + +form { + display: inline; +} diff --git a/src/main/resources/static/css/login.css b/src/main/resources/static/css/login.css new file mode 100644 index 00000000..9e004565 --- /dev/null +++ b/src/main/resources/static/css/login.css @@ -0,0 +1,71 @@ +/* 기본 스타일 설정 */ +body { + font-family: Arial, sans-serif; + background-color: #f4f7fb; /* 연한 배경색 */ + margin: 0; + padding: 0; + color: #333; + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + flex-direction: column; +} + +h1 { + font-size: 32px; + color: #344fd9; + margin-bottom: 40px; + text-align: center; +} + +form { + background-color: #ffffff; + padding: 40px; + border-radius: 8px; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); + width: 100%; + max-width: 400px; +} + + +form div { + margin-bottom: 20px; +} + + +label { + display: block; + margin-bottom: 5px; + font-weight: 600; + color: #555; +} + + +input[type="email"], input[type="password"] { + width: 100%; + padding: 12px; + font-size: 16px; + border: 1px solid #ddd; + border-radius: 4px; + box-sizing: border-box; + transition: border-color 0.3s; +} + + +input[type="email"]:focus, input[type="password"]:focus { + border-color: #344fd9; + outline: none; +} + + +button { + width: 100%; + padding: 12px; + font-size: 16px; + background-color: #344fd9; + color: #ffffff; + border: none; + border-radius: 4px; + cursor: pointer; +} diff --git a/src/main/resources/templates/dummy/login.html b/src/main/resources/templates/dummy/login.html index 99921f8d..29aaa17e 100644 --- a/src/main/resources/templates/dummy/login.html +++ b/src/main/resources/templates/dummy/login.html @@ -9,24 +9,24 @@

더미데이터 유저 로그인

해당 페이지는 개발자용 페이지입니다. 더미데이터 유저만 로그인이 가능합니다.

-

+

-
+

-

+

- - + +
diff --git a/src/main/resources/templates/point/charge.html b/src/main/resources/templates/point/charge.html index 61414c1c..d4edcb17 100644 --- a/src/main/resources/templates/point/charge.html +++ b/src/main/resources/templates/point/charge.html @@ -1,12 +1,12 @@ - + 충전 페이지 - +

포인트 충전 관리

diff --git a/src/main/resources/templates/point/header.html b/src/main/resources/templates/point/header.html new file mode 100644 index 00000000..227f9a43 --- /dev/null +++ b/src/main/resources/templates/point/header.html @@ -0,0 +1,23 @@ + + + + + + + Admin Header + + +
+
+

Admin Dashboard

+ +
+
+ + diff --git a/src/main/resources/templates/point/login.html b/src/main/resources/templates/point/login.html new file mode 100644 index 00000000..895b479e --- /dev/null +++ b/src/main/resources/templates/point/login.html @@ -0,0 +1,35 @@ + + + + + + Admin Login + + +

Admin Login

+ +
+
+ + +
+
+ + +
+ +
+ +
+ + + + diff --git a/src/main/resources/templates/point/withdraw.html b/src/main/resources/templates/point/withdraw.html index 14ea5bd5..5c4e86fb 100644 --- a/src/main/resources/templates/point/withdraw.html +++ b/src/main/resources/templates/point/withdraw.html @@ -1,12 +1,12 @@ - + 출금 페이지 - +

포인트 출금 관리

diff --git a/src/test/java/com/example/sinitto/auth/service/TokenServiceTest.java b/src/test/java/com/example/sinitto/auth/service/TokenServiceTest.java index d14be2ff..87e087be 100644 --- a/src/test/java/com/example/sinitto/auth/service/TokenServiceTest.java +++ b/src/test/java/com/example/sinitto/auth/service/TokenServiceTest.java @@ -80,7 +80,7 @@ void generateRefreshTokenTest() { @Test @DisplayName("deleteAllDataFromRedis 메소드 테스트") - void deleteAllDataFromRedisTest(){ + void deleteAllDataFromRedisTest() { //given String email = "test@email.com"; String refreshToken = "test.Refresh.Token";