Skip to content

Commit 1dacdbd

Browse files
author
김동학
authored
Merge pull request #314 from GSM-MSG/feature/313-generate-student-info-link
학생정보 조회 링크 생성 API 개발
2 parents 4005f84 + 0147046 commit 1dacdbd

File tree

21 files changed

+260
-2
lines changed

21 files changed

+260
-2
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package team.msg.sms.common.util
2+
3+
import java.security.SecureRandom
4+
import java.util.stream.Collectors
5+
import java.util.stream.IntStream
6+
7+
object PasswordUtil {
8+
// 소문자와 숫자로 이루어진 length 만큼의 길이를 가진 문자열을 생성하는 함수
9+
fun generateSecret(length: Int): String {
10+
val secureRandom = SecureRandom()
11+
12+
val password = IntStream.concat(
13+
IntStream.rangeClosed(48, 57),
14+
IntStream.rangeClosed(97, 122)
15+
).mapToObj { i -> i.toChar().toString() }.collect(Collectors.joining())
16+
17+
val builder = StringBuilder()
18+
19+
for (i in 0 until length) {
20+
builder.append(password[secureRandom.nextInt(password.length)])
21+
}
22+
23+
return builder.toString()
24+
}
25+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package team.msg.sms.domain.student.dto.req
2+
3+
import java.util.UUID
4+
5+
data class CreateStudentLinkRequestData (
6+
val studentId: UUID,
7+
val periodDay: Long
8+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package team.msg.sms.domain.student.dto.res
2+
3+
data class CreateStudentLinkResponseData (
4+
val token: String
5+
)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package team.msg.sms.domain.student.model
2+
3+
import team.msg.sms.common.annotation.Aggregate
4+
import java.util.UUID
5+
6+
@Aggregate
7+
data class StudentLink(
8+
val token: String,
9+
val studentId: UUID,
10+
val timeToLive: Long
11+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package team.msg.sms.domain.student.service
2+
3+
interface CheckStudentLinkService {
4+
fun checkExistsByToken(token: String): Boolean
5+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package team.msg.sms.domain.student.service
2+
3+
import team.msg.sms.domain.student.model.StudentLink
4+
5+
interface CommandStudentLinkService {
6+
fun save(
7+
studentLink: StudentLink
8+
): StudentLink
9+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package team.msg.sms.domain.student.service
2+
3+
import team.msg.sms.common.annotation.Service
4+
5+
@Service
6+
class StudentLinkService (
7+
commandStudentLinkService: CommandStudentLinkService,
8+
checkStudentLinkService: CheckStudentLinkService
9+
): CommandStudentLinkService by commandStudentLinkService,
10+
CheckStudentLinkService by checkStudentLinkService
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package team.msg.sms.domain.student.service.impl
2+
3+
import team.msg.sms.common.annotation.Service
4+
import team.msg.sms.domain.student.service.CheckStudentLinkService
5+
import team.msg.sms.domain.student.spi.StudentLinkPort
6+
7+
@Service
8+
class CheckStudentLinkServiceImpl (
9+
private val studentLinkPort: StudentLinkPort
10+
) : CheckStudentLinkService {
11+
override fun checkExistsByToken(token: String): Boolean {
12+
return studentLinkPort.existsByToken(token)
13+
}
14+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package team.msg.sms.domain.student.service.impl
2+
3+
import team.msg.sms.common.annotation.Service
4+
import team.msg.sms.domain.student.model.StudentLink
5+
import team.msg.sms.domain.student.service.CommandStudentLinkService
6+
import team.msg.sms.domain.student.spi.StudentLinkPort
7+
8+
@Service
9+
class CommandStudentLinkServiceImpl(
10+
private val studentLinkPort: StudentLinkPort
11+
): CommandStudentLinkService {
12+
override fun save(studentLink: StudentLink): StudentLink {
13+
return studentLinkPort.save(studentLink)
14+
}
15+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package team.msg.sms.domain.student.spi
2+
3+
import team.msg.sms.domain.student.model.StudentLink
4+
5+
interface CommandStudentLinkPort {
6+
fun save(studentLink: StudentLink): StudentLink
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package team.msg.sms.domain.student.spi
2+
3+
interface QueryStudentLinkPort {
4+
fun existsByToken(token: String): Boolean
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package team.msg.sms.domain.student.spi
2+
3+
interface StudentLinkPort:
4+
CommandStudentLinkPort,
5+
QueryStudentLinkPort
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package team.msg.sms.domain.student.usecase
2+
3+
import team.msg.sms.common.annotation.UseCase
4+
import team.msg.sms.common.util.PasswordUtil.generateSecret
5+
import team.msg.sms.domain.student.dto.req.CreateStudentLinkRequestData
6+
import team.msg.sms.domain.student.dto.res.CreateStudentLinkResponseData
7+
import team.msg.sms.domain.student.model.StudentLink
8+
import team.msg.sms.domain.student.service.StudentLinkService
9+
import team.msg.sms.domain.student.service.StudentService
10+
import java.util.*
11+
12+
@UseCase
13+
class CreateStudentLinkUseCase (
14+
private val studentService: StudentService,
15+
private val studentLinkService: StudentLinkService,
16+
) {
17+
fun execute(createStudentLinkData: CreateStudentLinkRequestData): CreateStudentLinkResponseData {
18+
val student = studentService.getStudentById(createStudentLinkData.studentId)
19+
val token = generateSecret(32)
20+
21+
val studentLink = StudentLink(
22+
token = token,
23+
studentId = student.id,
24+
timeToLive = createStudentLinkData.periodDay * 86400
25+
)
26+
studentLinkService.save(studentLink)
27+
28+
return CreateStudentLinkResponseData(
29+
token = token
30+
)
31+
}
32+
}

sms-infrastructure/src/main/kotlin/team/msg/sms/global/security/SecurityConfig.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ class SecurityConfig(
6161
.antMatchers(HttpMethod.POST, "/student").hasAuthority(STUDENT)
6262
.antMatchers(HttpMethod.GET, "/student").permitAll()
6363
.antMatchers(HttpMethod.GET, "/student/{uuid}").hasAnyAuthority(STUDENT, TEACHER)
64+
.antMatchers(HttpMethod.POST, "/student/link").hasAuthority(TEACHER)
6465
.antMatchers(HttpMethod.GET, "/student/anonymous/{uuid}").permitAll()
6566

6667
.antMatchers(HttpMethod.POST, "/teacher/common").hasAuthority(TEACHER)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package team.msg.sms.persistence.student
2+
3+
import org.springframework.stereotype.Component
4+
import team.msg.sms.domain.student.model.StudentLink
5+
import team.msg.sms.domain.student.spi.StudentLinkPort
6+
import team.msg.sms.persistence.student.mapper.toDomain
7+
import team.msg.sms.persistence.student.mapper.toEntity
8+
import team.msg.sms.persistence.student.repository.StudentJpaRepository
9+
import team.msg.sms.persistence.student.repository.StudentLinkRepository
10+
11+
@Component
12+
class StudentLinkPersistenceAdapter(
13+
private val studentLinkRepository: StudentLinkRepository
14+
) : StudentLinkPort {
15+
override fun save(studentLink: StudentLink): StudentLink {
16+
return studentLinkRepository.save(
17+
studentLink.toEntity()
18+
).toDomain()
19+
}
20+
21+
override fun existsByToken(token: String): Boolean {
22+
return studentLinkRepository.existsByToken(token)
23+
}
24+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package team.msg.sms.persistence.student.entity
2+
3+
import org.springframework.data.annotation.Id
4+
import org.springframework.data.redis.core.RedisHash
5+
import org.springframework.data.redis.core.TimeToLive
6+
import org.springframework.data.redis.core.index.Indexed
7+
import java.util.UUID
8+
9+
@RedisHash(value = "student_link")
10+
class StudentLinkEntity (
11+
@Id
12+
@Indexed
13+
val token: String,
14+
15+
@Indexed
16+
val studentId: UUID,
17+
18+
@TimeToLive
19+
val timeToLive: Long
20+
)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package team.msg.sms.persistence.student.mapper
2+
3+
import team.msg.sms.domain.auth.model.RefreshToken
4+
import team.msg.sms.domain.student.model.StudentLink
5+
import team.msg.sms.persistence.auth.entity.RefreshTokenEntity
6+
import team.msg.sms.persistence.student.entity.StudentLinkEntity
7+
8+
fun StudentLinkEntity.toDomain(): StudentLink =
9+
StudentLink(
10+
token = token,
11+
studentId = studentId,
12+
timeToLive = timeToLive
13+
)
14+
15+
fun StudentLink.toEntity(): StudentLinkEntity =
16+
StudentLinkEntity(
17+
token = token,
18+
studentId = studentId,
19+
timeToLive = timeToLive
20+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package team.msg.sms.persistence.student.repository
2+
3+
import org.springframework.data.repository.CrudRepository
4+
import org.springframework.stereotype.Repository
5+
import team.msg.sms.persistence.student.entity.StudentLinkEntity
6+
7+
@Repository
8+
interface StudentLinkRepository : CrudRepository<StudentLinkEntity, Long> {
9+
fun existsByToken(token: String): Boolean
10+
}

sms-presentation/src/main/kotlin/team/msg/sms/domain/student/StudentWebAdapter.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package team.msg.sms.domain.student
33
import org.springframework.http.ResponseEntity
44
import org.springframework.web.bind.annotation.*
55
import team.msg.sms.common.exception.InvalidUuidException
6+
import team.msg.sms.domain.student.dto.req.CreateStudentLinkWebRequest
67
import team.msg.sms.domain.student.dto.req.FindAllFiltersWebRequest
78
import team.msg.sms.domain.student.dto.req.ModifyStudentInfoWebRequest
89
import team.msg.sms.domain.student.dto.req.SignUpWebRequest
@@ -19,7 +20,8 @@ class StudentWebAdapter(
1920
private val studentInfoAnonymousUseCase: StudentInfoAnonymousUseCase,
2021
private val studentInfoDetailUseCase: StudentInfoDetailUseCase,
2122
private val studentInfoTeacherUseCase: StudentInfoTeacherUseCase,
22-
private val modifyStudentInfoUseCase: ModifyStudentInfoUseCase
23+
private val modifyStudentInfoUseCase: ModifyStudentInfoUseCase,
24+
private val createStudentLinkUseCase: CreateStudentLinkUseCase
2325
) {
2426
@GetMapping
2527
fun findAll(
@@ -35,6 +37,12 @@ class StudentWebAdapter(
3537
signUpUseCase.execute(signUpWebRequest.toData())
3638
.run { ResponseEntity.ok().build() }
3739

40+
@PostMapping("/link")
41+
fun createStudentLink(@RequestBody @Valid createStudentLinkWebRequest: CreateStudentLinkWebRequest): ResponseEntity<CreateStudentLinkWebResponse> {
42+
return createStudentLinkUseCase.execute(createStudentLinkWebRequest.toData())
43+
.let { ResponseEntity.ok(it.toResponse()) }
44+
}
45+
3846
@PutMapping
3947
fun modifyStudentInfo(@RequestBody modifyStudentInfoWebRequest: ModifyStudentInfoWebRequest) {
4048
modifyStudentInfoUseCase.execute(modifyStudentInfoWebRequest.toData())
@@ -125,6 +133,11 @@ class StudentWebAdapter(
125133
prizes = this.prizes
126134
)
127135

136+
fun CreateStudentLinkResponseData.toResponse(): CreateStudentLinkWebResponse =
137+
CreateStudentLinkWebResponse(
138+
token = this.token
139+
)
140+
128141
private fun isValidUUID(uuid: String): Boolean {
129142
return try {
130143
UUID.fromString(uuid)
@@ -133,4 +146,4 @@ class StudentWebAdapter(
133146
false
134147
}
135148
}
136-
}
149+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package team.msg.sms.domain.student.dto.req
2+
3+
import java.util.UUID
4+
5+
data class CreateStudentLinkWebRequest (
6+
val studentId: UUID,
7+
val periodDay: Long
8+
) {
9+
fun toData(): CreateStudentLinkRequestData =
10+
CreateStudentLinkRequestData(
11+
studentId,
12+
periodDay
13+
)
14+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package team.msg.sms.domain.student.dto.res
2+
3+
data class CreateStudentLinkWebResponse (
4+
val token: String
5+
)

0 commit comments

Comments
 (0)