diff --git a/bitgouel-api/src/main/kotlin/team/msg/common/util/UserUtil.kt b/bitgouel-api/src/main/kotlin/team/msg/common/util/UserUtil.kt new file mode 100644 index 000000000..d3dea2348 --- /dev/null +++ b/bitgouel-api/src/main/kotlin/team/msg/common/util/UserUtil.kt @@ -0,0 +1,29 @@ +package team.msg.common.util + +import org.springframework.data.repository.findByIdOrNull +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.stereotype.Component +import team.msg.domain.user.exception.UserNotFoundException +import team.msg.domain.user.model.User +import team.msg.domain.user.repository.UserRepository +import team.msg.global.security.principal.AuthDetails +import java.util.* + +@Component +class UserUtil( + private val userRepository: UserRepository +) { + fun queryCurrentUser(): User { + val principal = SecurityContextHolder.getContext().authentication.principal + + val userId = UUID.fromString(if(principal is AuthDetails) + principal.username + else + principal.toString()) + + val user = userRepository.findByIdOrNull(userId) + ?: throw UserNotFoundException("존재하지 않는 유저입니다. : [ id = $userId ]") + + return user + } +} \ No newline at end of file diff --git a/bitgouel-api/src/main/kotlin/team/msg/domain/auth/service/AuthServiceImpl.kt b/bitgouel-api/src/main/kotlin/team/msg/domain/auth/service/AuthServiceImpl.kt index 526842fbb..fa45f6d06 100644 --- a/bitgouel-api/src/main/kotlin/team/msg/domain/auth/service/AuthServiceImpl.kt +++ b/bitgouel-api/src/main/kotlin/team/msg/domain/auth/service/AuthServiceImpl.kt @@ -243,10 +243,10 @@ class AuthServiceImpl( */ private fun queryClub(highSchool: HighSchool, clubName: String): Club { val school = schoolRepository.findByHighSchool(highSchool) - ?: throw SchoolNotFoundException("존재하지 않는 학교입니다. values : [ highSchool = $highSchool ]") + ?: throw SchoolNotFoundException("존재하지 않는 학교입니다. info : [ highSchool = $highSchool ]") val club = clubRepository.findByNameAndSchool(clubName, school) - ?: throw ClubNotFoundException("존재하지 않는 동아리입니다. values : [ club = $clubName ]") + ?: throw ClubNotFoundException("존재하지 않는 동아리입니다. info : [ club = $clubName ]") return club } diff --git a/bitgouel-api/src/main/kotlin/team/msg/domain/student/exception/StudentNotFoundException.kt b/bitgouel-api/src/main/kotlin/team/msg/domain/student/exception/StudentNotFoundException.kt new file mode 100644 index 000000000..2488bbf6d --- /dev/null +++ b/bitgouel-api/src/main/kotlin/team/msg/domain/student/exception/StudentNotFoundException.kt @@ -0,0 +1,8 @@ +package team.msg.domain.student.exception + +import team.msg.domain.student.exception.constant.StudentErrorCode +import team.msg.global.error.exception.BitgouelException + +class StudentNotFoundException( + message: String +) : BitgouelException(message, StudentErrorCode.STUDENT_NOT_FOUND.status) \ No newline at end of file diff --git a/bitgouel-api/src/main/kotlin/team/msg/domain/student/exception/constant/StudentErrorCode.kt b/bitgouel-api/src/main/kotlin/team/msg/domain/student/exception/constant/StudentErrorCode.kt new file mode 100644 index 000000000..73f1548b2 --- /dev/null +++ b/bitgouel-api/src/main/kotlin/team/msg/domain/student/exception/constant/StudentErrorCode.kt @@ -0,0 +1,8 @@ +package team.msg.domain.student.exception.constant + +enum class StudentErrorCode( + val message: String, + val status: Int +) { + STUDENT_NOT_FOUND("학생을 찾을 수 없습니다.", 404) +} \ No newline at end of file diff --git a/bitgouel-api/src/main/kotlin/team/msg/domain/student/mapper/StudentActivityMapper.kt b/bitgouel-api/src/main/kotlin/team/msg/domain/student/mapper/StudentActivityMapper.kt new file mode 100644 index 000000000..81d47fbfe --- /dev/null +++ b/bitgouel-api/src/main/kotlin/team/msg/domain/student/mapper/StudentActivityMapper.kt @@ -0,0 +1,8 @@ +package team.msg.domain.student.mapper + +import team.msg.domain.student.presentation.data.request.CreateStudentActivityRequest +import team.msg.domain.student.presentation.data.web.CreateStudentActivityWebRequest + +interface StudentActivityMapper { + fun createStudentActivityWebRequestToDto(webRequest: CreateStudentActivityWebRequest): CreateStudentActivityRequest +} \ No newline at end of file diff --git a/bitgouel-api/src/main/kotlin/team/msg/domain/student/mapper/StudentActivityMapperImpl.kt b/bitgouel-api/src/main/kotlin/team/msg/domain/student/mapper/StudentActivityMapperImpl.kt new file mode 100644 index 000000000..cf62c681d --- /dev/null +++ b/bitgouel-api/src/main/kotlin/team/msg/domain/student/mapper/StudentActivityMapperImpl.kt @@ -0,0 +1,20 @@ +package team.msg.domain.student.mapper + +import org.springframework.stereotype.Component +import team.msg.domain.student.presentation.data.request.CreateStudentActivityRequest +import team.msg.domain.student.presentation.data.web.CreateStudentActivityWebRequest + +@Component +class StudentActivityMapperImpl : StudentActivityMapper { + + /** + * StudentActivity 생성 Web Request 를 애플리케이션 영역에서 사용될 Dto 로 매핑합니다. + */ + override fun createStudentActivityWebRequestToDto(webRequest: CreateStudentActivityWebRequest): CreateStudentActivityRequest = + CreateStudentActivityRequest( + title = webRequest.title, + content = webRequest.content, + credit = webRequest.credit, + activityDate = webRequest.activityDate + ) +} \ No newline at end of file diff --git a/bitgouel-api/src/main/kotlin/team/msg/domain/student/presentation/StudentActivityController.kt b/bitgouel-api/src/main/kotlin/team/msg/domain/student/presentation/StudentActivityController.kt new file mode 100644 index 000000000..e24ad56be --- /dev/null +++ b/bitgouel-api/src/main/kotlin/team/msg/domain/student/presentation/StudentActivityController.kt @@ -0,0 +1,25 @@ +package team.msg.domain.student.presentation + +import javax.validation.Valid +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController +import team.msg.domain.student.mapper.StudentActivityMapper +import team.msg.domain.student.presentation.data.web.CreateStudentActivityWebRequest +import team.msg.domain.student.service.StudentActivityService + +@RestController +@RequestMapping("/activity") +class StudentActivityController( + private val studentActivityService: StudentActivityService, + private val studentActivityMapper: StudentActivityMapper +) { + @PostMapping + fun createStudentActivity(@RequestBody @Valid request: CreateStudentActivityWebRequest): ResponseEntity { + studentActivityService.createStudentActivity(studentActivityMapper.createStudentActivityWebRequestToDto(request)) + return ResponseEntity.status(HttpStatus.CREATED).build() + } +} \ No newline at end of file diff --git a/bitgouel-api/src/main/kotlin/team/msg/domain/student/presentation/data/request/CreateStudentActivityRequest.kt b/bitgouel-api/src/main/kotlin/team/msg/domain/student/presentation/data/request/CreateStudentActivityRequest.kt new file mode 100644 index 000000000..c0a1c2946 --- /dev/null +++ b/bitgouel-api/src/main/kotlin/team/msg/domain/student/presentation/data/request/CreateStudentActivityRequest.kt @@ -0,0 +1,10 @@ +package team.msg.domain.student.presentation.data.request + +import java.time.LocalDateTime + +data class CreateStudentActivityRequest( + val title: String, + val content: String, + val credit: Int, + val activityDate: LocalDateTime +) \ No newline at end of file diff --git a/bitgouel-api/src/main/kotlin/team/msg/domain/student/presentation/data/web/CreateStudentActivityWebRequest.kt b/bitgouel-api/src/main/kotlin/team/msg/domain/student/presentation/data/web/CreateStudentActivityWebRequest.kt new file mode 100644 index 000000000..534dc9e66 --- /dev/null +++ b/bitgouel-api/src/main/kotlin/team/msg/domain/student/presentation/data/web/CreateStudentActivityWebRequest.kt @@ -0,0 +1,22 @@ +package team.msg.domain.student.presentation.data.web + +import javax.validation.constraints.Max +import javax.validation.constraints.NotBlank +import javax.validation.constraints.NotNull +import java.time.LocalDateTime + +data class CreateStudentActivityWebRequest( + @field:NotBlank + @field:Max(100) + val title: String, + + @field:NotBlank + @field:Max(1000) + val content: String, + + @field:NotNull + val credit: Int, + + @field:NotNull + val activityDate: LocalDateTime +) \ No newline at end of file diff --git a/bitgouel-api/src/main/kotlin/team/msg/domain/student/service/StudentActivityService.kt b/bitgouel-api/src/main/kotlin/team/msg/domain/student/service/StudentActivityService.kt new file mode 100644 index 000000000..71682afd0 --- /dev/null +++ b/bitgouel-api/src/main/kotlin/team/msg/domain/student/service/StudentActivityService.kt @@ -0,0 +1,7 @@ +package team.msg.domain.student.service + +import team.msg.domain.student.presentation.data.request.CreateStudentActivityRequest + +interface StudentActivityService { + fun createStudentActivity(request: CreateStudentActivityRequest) +} \ No newline at end of file diff --git a/bitgouel-api/src/main/kotlin/team/msg/domain/student/service/StudentActivityServiceImpl.kt b/bitgouel-api/src/main/kotlin/team/msg/domain/student/service/StudentActivityServiceImpl.kt new file mode 100644 index 000000000..c3674ef50 --- /dev/null +++ b/bitgouel-api/src/main/kotlin/team/msg/domain/student/service/StudentActivityServiceImpl.kt @@ -0,0 +1,51 @@ +package team.msg.domain.student.service + +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import team.msg.common.enum.ApproveStatus +import team.msg.common.util.UserUtil +import team.msg.domain.student.exception.StudentNotFoundException +import team.msg.domain.student.model.StudentActivity +import team.msg.domain.student.presentation.data.request.CreateStudentActivityRequest +import team.msg.domain.student.repository.StudentActivityRepository +import team.msg.domain.student.repository.StudentRepository +import team.msg.domain.teacher.exception.TeacherNotFoundException +import team.msg.domain.teacher.repository.TeacherRepository +import java.util.* + +@Service +class StudentActivityServiceImpl( + private val userUtil: UserUtil, + private val studentRepository: StudentRepository, + private val teacherRepository: TeacherRepository, + private val studentActivityRepository: StudentActivityRepository +) : StudentActivityService { + + /** + * 학생 활동을 생성하는 비지니스 로직입니다 + * @param CreateStudentActivityRequest + */ + @Transactional(rollbackFor = [Exception::class]) + override fun createStudentActivity(request: CreateStudentActivityRequest) { + val user = userUtil.queryCurrentUser() + + val student = studentRepository.findByUser(user) + ?: throw StudentNotFoundException("학생을 찾을 수 없습니다. info : [ name = ${user.name} ]") + + val teacher = teacherRepository.findByClub(student.club) + ?: throw TeacherNotFoundException("취업 동아리 선생님을 찾을 수 없습니다.") + + val studentActivity = StudentActivity( + id = UUID.randomUUID(), + title = request.title, + content = request.content, + credit = request.credit, + activityDate = request.activityDate, + student = student, + teacher = teacher, + approveStatus = ApproveStatus.PENDING + ) + + studentActivityRepository.save(studentActivity) + } +} \ No newline at end of file diff --git a/bitgouel-api/src/main/kotlin/team/msg/domain/teacher/exception/TeacherNotFoundException.kt b/bitgouel-api/src/main/kotlin/team/msg/domain/teacher/exception/TeacherNotFoundException.kt new file mode 100644 index 000000000..428f6079f --- /dev/null +++ b/bitgouel-api/src/main/kotlin/team/msg/domain/teacher/exception/TeacherNotFoundException.kt @@ -0,0 +1,9 @@ +package team.msg.domain.teacher.exception + +import team.msg.domain.teacher.exception.constant.TeacherErrorCode +import team.msg.global.error.exception.BitgouelException + +class TeacherNotFoundException( + message: String +) : BitgouelException(message, TeacherErrorCode.TEACHER_NOT_FOUND.status) { +} \ No newline at end of file diff --git a/bitgouel-api/src/main/kotlin/team/msg/domain/teacher/exception/constant/TeacherErrorCode.kt b/bitgouel-api/src/main/kotlin/team/msg/domain/teacher/exception/constant/TeacherErrorCode.kt new file mode 100644 index 000000000..d711d6bfa --- /dev/null +++ b/bitgouel-api/src/main/kotlin/team/msg/domain/teacher/exception/constant/TeacherErrorCode.kt @@ -0,0 +1,8 @@ +package team.msg.domain.teacher.exception.constant + +enum class TeacherErrorCode( + val message: String, + val status: Int +) { + TEACHER_NOT_FOUND("취업 동아리 선생님을 찾을 수 없습니다.", 404) +} \ No newline at end of file diff --git a/bitgouel-api/src/main/kotlin/team/msg/global/security/SecurityConfig.kt b/bitgouel-api/src/main/kotlin/team/msg/global/security/SecurityConfig.kt index fb624343e..7cce2b298 100644 --- a/bitgouel-api/src/main/kotlin/team/msg/global/security/SecurityConfig.kt +++ b/bitgouel-api/src/main/kotlin/team/msg/global/security/SecurityConfig.kt @@ -18,6 +18,11 @@ import org.springframework.web.cors.CorsUtils class SecurityConfig( private val jwtTokenParser: JwtTokenParser ) { + companion object { + const val ADMIN = "ADMIN" + const val STUDENT = "STUDENT" + } + @Bean protected fun filterChain(http: HttpSecurity): SecurityFilterChain = http @@ -42,6 +47,9 @@ class SecurityConfig( .mvcMatchers(HttpMethod.POST, "/auth/login").permitAll() .mvcMatchers(HttpMethod.PATCH, "/auth").permitAll() + // activity + .mvcMatchers(HttpMethod.POST, "/activity").hasRole(STUDENT) + .anyRequest().authenticated() .and() diff --git a/bitgouel-domain/src/main/kotlin/team/msg/domain/student/model/StudentActivity.kt b/bitgouel-domain/src/main/kotlin/team/msg/domain/student/model/StudentActivity.kt index 579dd32cf..37f32be97 100644 --- a/bitgouel-domain/src/main/kotlin/team/msg/domain/student/model/StudentActivity.kt +++ b/bitgouel-domain/src/main/kotlin/team/msg/domain/student/model/StudentActivity.kt @@ -31,7 +31,8 @@ class StudentActivity( @Column(columnDefinition = "VARCHAR(10)", nullable = false) val approveStatus: ApproveStatus, - override val createdAt: LocalDateTime, + @Column(nullable = false, updatable = false, columnDefinition = "DATETIME(6)") + val activityDate: LocalDateTime, @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "student_id", columnDefinition = "BINARY(16)", nullable = false) diff --git a/bitgouel-domain/src/main/kotlin/team/msg/domain/student/repository/StudentRepository.kt b/bitgouel-domain/src/main/kotlin/team/msg/domain/student/repository/StudentRepository.kt index 1b63a8ea1..c6ec190b5 100644 --- a/bitgouel-domain/src/main/kotlin/team/msg/domain/student/repository/StudentRepository.kt +++ b/bitgouel-domain/src/main/kotlin/team/msg/domain/student/repository/StudentRepository.kt @@ -2,7 +2,9 @@ package team.msg.domain.student.repository import org.springframework.data.repository.CrudRepository import team.msg.domain.student.model.Student +import team.msg.domain.user.model.User import java.util.UUID interface StudentRepository : CrudRepository { + fun findByUser(user: User): Student? } \ No newline at end of file diff --git a/bitgouel-domain/src/main/kotlin/team/msg/domain/teacher/repository/TeacherRepository.kt b/bitgouel-domain/src/main/kotlin/team/msg/domain/teacher/repository/TeacherRepository.kt index f70e69666..ad3db16ec 100644 --- a/bitgouel-domain/src/main/kotlin/team/msg/domain/teacher/repository/TeacherRepository.kt +++ b/bitgouel-domain/src/main/kotlin/team/msg/domain/teacher/repository/TeacherRepository.kt @@ -1,8 +1,10 @@ package team.msg.domain.teacher.repository import org.springframework.data.repository.CrudRepository +import team.msg.domain.club.model.Club import team.msg.domain.teacher.model.Teacher import java.util.UUID interface TeacherRepository : CrudRepository { + fun findByClub(club: Club): Teacher? } \ No newline at end of file