feat(identity): аккаунты преподавателей

This commit is contained in:
2026-10-27 15:46:48 +03:00
parent 6f8add3736
commit a8fa566d3c
5 changed files with 328 additions and 0 deletions

View File

@@ -13,4 +13,6 @@ interface AccountRepository : JpaRepository<Account, UUID> {
fun existsByRole(role: LmsRole): Boolean
fun countByRole(role: LmsRole): Long
fun findAllByRoleOrderByLoginAsc(role: LmsRole): List<Account>
}

View File

@@ -0,0 +1,30 @@
package ru.ysu.lms.identity.roster
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
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 java.util.UUID
@RestController
@RequestMapping("/api/v1/teachers")
@PreAuthorize("hasRole('ADMIN')")
class TeacherController(
private val teacherService: TeacherService,
) {
@GetMapping
fun list(): List<TeacherDto> = teacherService.list()
@PostMapping
fun create(
@RequestBody body: TeacherCreateRequest,
): TeacherCredential = teacherService.create(body)
@PostMapping("/{id}/reset-password")
fun resetPassword(
@PathVariable id: UUID,
): ResetPasswordResponse = teacherService.resetPassword(id)
}

View File

@@ -0,0 +1,22 @@
package ru.ysu.lms.identity.roster
/** Преподаватель в списке управления: аккаунт роли TEACHER. */
data class TeacherDto(
val id: String,
val login: String,
val name: String,
val status: String,
val mustChangePassword: Boolean,
)
data class TeacherCreateRequest(
val login: String,
val password: String? = null,
)
/** Выданные преподавателю учётные данные (пароль показывается один раз). */
data class TeacherCredential(
val id: String,
val login: String,
val password: String,
)

View File

@@ -0,0 +1,81 @@
package ru.ysu.lms.identity.roster
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import ru.ysu.lms.common.BadRequestException
import ru.ysu.lms.common.ConflictException
import ru.ysu.lms.common.NotFoundException
import ru.ysu.lms.identity.auth.Passwords
import ru.ysu.lms.identity.domain.Account
import ru.ysu.lms.identity.domain.AccountStatus
import ru.ysu.lms.identity.repo.AccountRepository
import ru.ysu.lms.security.LmsRole
import java.time.Instant
import java.util.UUID
@Service
class TeacherService(
private val accounts: AccountRepository,
private val passwordEncoder: PasswordEncoder,
) {
@Transactional(readOnly = true)
fun list(): List<TeacherDto> =
accounts
.findAllByRoleOrderByLoginAsc(LmsRole.TEACHER)
.map { account ->
TeacherDto(
id = account.id.toString(),
login = account.login,
name = account.login,
status = account.status.name,
mustChangePassword = account.mustChangePassword,
)
}
@Transactional
fun create(request: TeacherCreateRequest): TeacherCredential {
val login = request.login.trim()
if (login.length < 3) {
throw BadRequestException("Логин: минимум 3 символа")
}
if (accounts.existsByLogin(login)) {
throw ConflictException("Логин $login уже занят другим аккаунтом")
}
val password = request.password?.takeIf { it.isNotBlank() } ?: Passwords.generate()
val account =
accounts.save(
Account(
login = login,
passwordHash = passwordEncoder.encode(password) ?: "",
role = LmsRole.TEACHER,
mustChangePassword = true,
tempPassword = password,
status = AccountStatus.ACTIVE,
),
)
return TeacherCredential(
id = account.id.toString(),
login = account.login,
password = password,
)
}
@Transactional
fun resetPassword(teacherId: UUID): ResetPasswordResponse {
val account =
accounts
.findById(teacherId)
.orElseThrow { NotFoundException("Преподаватель не найден") }
if (account.role != LmsRole.TEACHER) {
throw BadRequestException("Сбросить пароль можно только преподавателю")
}
val password = Passwords.generate()
account.passwordHash = passwordEncoder.encode(password) ?: account.passwordHash
account.mustChangePassword = true
account.tempPassword = password
account.updatedAt = Instant.now()
accounts.save(account)
return ResetPasswordResponse(password)
}
}