Compare commits
2 commits
master
...
spring-con
Author | SHA1 | Date | |
---|---|---|---|
638c360975 | |||
140edcbb6d |
12 changed files with 198 additions and 85 deletions
|
@ -21,7 +21,7 @@ repositories {
|
||||||
val exposedVersion = "0.41.1"
|
val exposedVersion = "0.41.1"
|
||||||
val postgresVersion = "42.5.4"
|
val postgresVersion = "42.5.4"
|
||||||
val telegramBotVersion = "6.5.0"
|
val telegramBotVersion = "6.5.0"
|
||||||
val springBootVersion = "3.1.0"
|
val springBootVersion = "2.7.14"
|
||||||
val serializationVersion = "1.5.0"
|
val serializationVersion = "1.5.0"
|
||||||
val loggingVersion = "3.0.5"
|
val loggingVersion = "3.0.5"
|
||||||
val securityTestVersion = "6.0.2"
|
val securityTestVersion = "6.0.2"
|
||||||
|
@ -34,16 +34,9 @@ dependencies {
|
||||||
implementation("org.telegram:telegrambotsextensions:$telegramBotVersion")
|
implementation("org.telegram:telegrambotsextensions:$telegramBotVersion")
|
||||||
implementation("org.telegram:telegrambots-spring-boot-starter:$telegramBotVersion")
|
implementation("org.telegram:telegrambots-spring-boot-starter:$telegramBotVersion")
|
||||||
|
|
||||||
implementation("org.springframework.boot:spring-boot-starter:$springBootVersion") {
|
implementation("org.springframework.boot:spring-boot-starter:$springBootVersion")
|
||||||
exclude("org.springframework.boot:spring-boot-starter-tomcat")
|
implementation("org.springframework.boot:spring-boot-starter-web:$springBootVersion")
|
||||||
}
|
implementation("org.springframework.boot:spring-boot-starter-security:$springBootVersion")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-web:$springBootVersion") {
|
|
||||||
exclude("org.springframework.boot:spring-boot-starter-tomcat")
|
|
||||||
}
|
|
||||||
implementation("org.springframework.boot:spring-boot-starter-security:$springBootVersion") {
|
|
||||||
exclude("org.springframework.boot:spring-boot-starter-tomcat")
|
|
||||||
}
|
|
||||||
implementation("org.springframework.boot:spring-boot-starter-jetty:$springBootVersion")
|
|
||||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion")
|
||||||
|
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||||
|
@ -58,16 +51,6 @@ dependencies {
|
||||||
testImplementation("org.springframework.boot:spring-boot-starter-test:$springBootVersion")
|
testImplementation("org.springframework.boot:spring-boot-starter-test:$springBootVersion")
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations.all {
|
|
||||||
resolutionStrategy {
|
|
||||||
eachDependency {
|
|
||||||
if (requested.group == "jakarta.servlet") {
|
|
||||||
useVersion("5.0.0")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.withType<KotlinCompile> {
|
tasks.withType<KotlinCompile> {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
freeCompilerArgs = listOf("-Xjsr305=strict")
|
freeCompilerArgs = listOf("-Xjsr305=strict")
|
||||||
|
|
|
@ -3,19 +3,18 @@ package ru.sicamp.sicamphelper.api.command
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import mu.KLogging
|
import mu.KLogging
|
||||||
import org.telegram.telegrambots.extensions.bots.commandbot.commands.BotCommand
|
import org.telegram.telegrambots.extensions.bots.commandbot.commands.BotCommand
|
||||||
import org.telegram.telegrambots.meta.api.methods.send.SendMessage
|
|
||||||
import org.telegram.telegrambots.meta.api.objects.Chat
|
import org.telegram.telegrambots.meta.api.objects.Chat
|
||||||
import org.telegram.telegrambots.meta.api.objects.User
|
|
||||||
import org.telegram.telegrambots.meta.bots.AbsSender
|
import org.telegram.telegrambots.meta.bots.AbsSender
|
||||||
import ru.sicamp.sicamphelper.model.metadata.RequestSource
|
import ru.sicamp.sicamphelper.db.entity.User
|
||||||
import ru.sicamp.sicamphelper.db.table.Users
|
import ru.sicamp.sicamphelper.db.table.Users
|
||||||
|
import ru.sicamp.sicamphelper.model.metadata.RequestSource
|
||||||
import ru.sicamp.sicamphelper.model.request.Request
|
import ru.sicamp.sicamphelper.model.request.Request
|
||||||
import ru.sicamp.sicamphelper.model.request.RequestWrapper
|
import ru.sicamp.sicamphelper.model.request.RequestWrapper
|
||||||
import ru.sicamp.sicamphelper.model.response.Response
|
|
||||||
import ru.sicamp.sicamphelper.model.response.ResponseWrapper
|
import ru.sicamp.sicamphelper.model.response.ResponseWrapper
|
||||||
import ru.sicamp.sicamphelper.model.response.TelegramResponse
|
import ru.sicamp.sicamphelper.model.response.TelegramResponse
|
||||||
import ru.sicamp.sicamphelper.service.UserService
|
import ru.sicamp.sicamphelper.service.UserService
|
||||||
import ru.sicamp.sicamphelper.util.Extractor
|
import ru.sicamp.sicamphelper.util.Extractor
|
||||||
|
import ru.sicamp.sicamphelper.util.TgUser
|
||||||
|
|
||||||
abstract class Command<REQ : Request, RES : TelegramResponse>(
|
abstract class Command<REQ : Request, RES : TelegramResponse>(
|
||||||
private val command: Commands
|
private val command: Commands
|
||||||
|
@ -24,7 +23,7 @@ abstract class Command<REQ : Request, RES : TelegramResponse>(
|
||||||
|
|
||||||
protected abstract suspend fun executeRequest(request: RequestWrapper<REQ>): ResponseWrapper<RES>
|
protected abstract suspend fun executeRequest(request: RequestWrapper<REQ>): ResponseWrapper<RES>
|
||||||
|
|
||||||
override fun execute(absSender: AbsSender?, user: User?, chat: Chat?, arguments: Array<out String>?) {
|
override fun execute(absSender: AbsSender?, user: TgUser?, chat: Chat?, arguments: Array<out String>?) {
|
||||||
val internalUser = authorizeCommand(user) ?: TODO("Respond unauthorized request")
|
val internalUser = authorizeCommand(user) ?: TODO("Respond unauthorized request")
|
||||||
val request = try {
|
val request = try {
|
||||||
RequestWrapper(
|
RequestWrapper(
|
||||||
|
@ -49,7 +48,7 @@ abstract class Command<REQ : Request, RES : TelegramResponse>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun authorizeCommand(user: User?): ru.sicamp.sicamphelper.db.entity.User? = userService.findUserByTgUser(
|
private fun authorizeCommand(user: TgUser?): User? = userService.findUserByTgUser(
|
||||||
user ?: error("No user provided for command $commandIdentifier")
|
user ?: error("No user provided for command $commandIdentifier")
|
||||||
).let {
|
).let {
|
||||||
if (it == null) {
|
if (it == null) {
|
||||||
|
|
|
@ -1,31 +1,25 @@
|
||||||
package ru.sicamp.sicamphelper.api.controller
|
package ru.sicamp.sicamphelper.api.controller
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import com.fasterxml.jackson.module.kotlin.convertValue
|
|
||||||
import mu.KLogging
|
import mu.KLogging
|
||||||
import org.springframework.web.bind.annotation.*
|
import org.springframework.web.bind.annotation.*
|
||||||
import ru.sicamp.sicamphelper.model.dto.UserDto
|
import ru.sicamp.sicamphelper.model.dto.UserDto
|
||||||
|
import ru.sicamp.sicamphelper.model.info.UserInfo
|
||||||
import ru.sicamp.sicamphelper.service.UserService
|
import ru.sicamp.sicamphelper.service.UserService
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
class UsersController(
|
class UsersController(
|
||||||
private val userService: UserService,
|
private val userService: UserService,
|
||||||
private val objectMapper: ObjectMapper,
|
|
||||||
) {
|
) {
|
||||||
@PostMapping("/user/create")
|
@PostMapping("/user/create")
|
||||||
fun create(@RequestParam dto: UserDto): String {
|
fun create(@RequestParam dto: UserDto): UserInfo {
|
||||||
val user = userService.createUser(dto)
|
return userService.createUser(dto)
|
||||||
return user.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/custom/users/find/{login}")
|
@GetMapping("/user/find/{login}")
|
||||||
fun getUser(@PathVariable(name = "login", required = true) login: String): String {
|
fun getUser(@PathVariable(name = "login", required = true) login: String): UserInfo {
|
||||||
logger.info("Getting user with username: $login!!!!!")
|
logger.info("Getting user with username: $login")
|
||||||
return objectMapper.convertValue(userService.loadUserByUsername(login))
|
return userService.findUserInfoByLogin(login)
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/test")
|
|
||||||
fun test() = "test"
|
|
||||||
|
|
||||||
companion object : KLogging()
|
companion object : KLogging()
|
||||||
}
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
package ru.sicamp.sicamphelper.model.data
|
package ru.sicamp.sicamphelper.model.data
|
||||||
|
|
||||||
|
import ru.sicamp.sicamphelper.util.TgUser
|
||||||
|
|
||||||
/**Class extracts user from user input. Also used to represent user in views. Format: @tgUsername|$gateLogin|#userId */
|
/**Class extracts user from user input. Also used to represent user in views. Format: @tgUsername|$gateLogin|#userId */
|
||||||
class UserInput(
|
class UserInput(
|
||||||
input: String
|
input: String
|
||||||
|
@ -19,6 +21,6 @@ class UserInput(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromTgUser(tgUser: org.telegram.telegrambots.meta.api.objects.User) = UserInput(Type.TG_USERNAME.prefix + tgUser.userName)
|
fun fromTgUser(tgUser: TgUser) = UserInput(Type.TG_USERNAME.prefix + tgUser.userName)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package ru.sicamp.sicamphelper.model.info
|
||||||
|
|
||||||
|
data class EntityRef(
|
||||||
|
val type: Info.Type,
|
||||||
|
val id: Long,
|
||||||
|
val name: String
|
||||||
|
)
|
10
src/main/kotlin/ru/sicamp/sicamphelper/model/info/Info.kt
Normal file
10
src/main/kotlin/ru/sicamp/sicamphelper/model/info/Info.kt
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package ru.sicamp.sicamphelper.model.info
|
||||||
|
|
||||||
|
sealed class Info {
|
||||||
|
abstract val id: Long
|
||||||
|
abstract val type: Type
|
||||||
|
|
||||||
|
enum class Type {
|
||||||
|
USER, GROUP, ROOM //TODO add entities
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package ru.sicamp.sicamphelper.model.info
|
||||||
|
|
||||||
|
import ru.sicamp.sicamphelper.db.entity.User
|
||||||
|
import ru.sicamp.sicamphelper.db.table.Users
|
||||||
|
|
||||||
|
data class UserInfo(
|
||||||
|
override val id: Long,
|
||||||
|
override val type: Type,
|
||||||
|
var login: String,
|
||||||
|
var name: String,
|
||||||
|
var supervisor: EntityRef?,
|
||||||
|
var tgId: Long?,
|
||||||
|
var tgUsername: String?,
|
||||||
|
var role: Users.Role,
|
||||||
|
var group: EntityRef,
|
||||||
|
var sex: Users.Sex,
|
||||||
|
var age: Int,
|
||||||
|
var enabled: Boolean,
|
||||||
|
) : Info() {
|
||||||
|
companion object {
|
||||||
|
fun from(user: User?) = user?.let {
|
||||||
|
UserInfo(
|
||||||
|
user.id.value,
|
||||||
|
Type.USER,
|
||||||
|
user.login,
|
||||||
|
user.name,
|
||||||
|
user.supervisor?.let {
|
||||||
|
EntityRef(
|
||||||
|
type = Type.USER,
|
||||||
|
id = it.id.value,
|
||||||
|
name = it.name
|
||||||
|
)
|
||||||
|
},
|
||||||
|
user.tgId,
|
||||||
|
user.tgUsername,
|
||||||
|
user.role,
|
||||||
|
EntityRef(
|
||||||
|
type = Type.GROUP,
|
||||||
|
id = user.group.id.value,
|
||||||
|
name = user.group.name
|
||||||
|
),
|
||||||
|
user.sex,
|
||||||
|
user.age,
|
||||||
|
user.enabled
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
package ru.sicamp.sicamphelper.repository
|
||||||
|
|
||||||
|
import mu.KLogging
|
||||||
|
import org.jetbrains.exposed.dao.load
|
||||||
|
import org.jetbrains.exposed.dao.with
|
||||||
|
import org.jetbrains.exposed.sql.and
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder
|
||||||
|
import org.springframework.stereotype.Repository
|
||||||
|
import ru.sicamp.sicamphelper.db.entity.User
|
||||||
|
import ru.sicamp.sicamphelper.db.table.Users
|
||||||
|
import ru.sicamp.sicamphelper.model.dto.UserDto
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
class UserRepository(
|
||||||
|
private val passwordEncoder: PasswordEncoder
|
||||||
|
) {
|
||||||
|
fun findUserByTgUsernameAndId(id: Long, userName: String) = transaction {
|
||||||
|
User.find {
|
||||||
|
(Users.tgId eq id) and
|
||||||
|
(Users.tgUsername eq userName)
|
||||||
|
}.with(
|
||||||
|
User::supervisor,
|
||||||
|
User::group
|
||||||
|
).firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findUserByUsername(username: String) = transaction {
|
||||||
|
User.find {
|
||||||
|
Users.login eq username
|
||||||
|
}.with(
|
||||||
|
User::supervisor,
|
||||||
|
User::group
|
||||||
|
).firstOrNull() ?: throw UsernameNotFoundException("Not found user with username $username")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findUserById(id: Long) = transaction {
|
||||||
|
User.findById(id)?.load(
|
||||||
|
User::supervisor,
|
||||||
|
User::group
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createUser(userDto: UserDto): User = transaction {
|
||||||
|
val now = LocalDateTime.now()
|
||||||
|
logger.info("Creating new user at $now")
|
||||||
|
return@transaction User.new {
|
||||||
|
login = userDto.login
|
||||||
|
_password = passwordEncoder.encode(userDto.password)
|
||||||
|
role = userDto.role
|
||||||
|
name = userDto.name
|
||||||
|
sex = userDto.sex
|
||||||
|
age = userDto.age
|
||||||
|
tgId = userDto.tgId
|
||||||
|
tgUsername = userDto.tgUsername
|
||||||
|
locked = false
|
||||||
|
enabled = userDto.enabled
|
||||||
|
accountExpiration = now + userExpirationTime
|
||||||
|
credentialsExpiration = now + credentialsExpirationTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object : KLogging() {
|
||||||
|
private val userExpirationTime = Duration.ofDays(60)
|
||||||
|
private val credentialsExpirationTime = Duration.ofDays(30)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package ru.sicamp.sicamphelper.security
|
||||||
|
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService
|
||||||
|
import org.springframework.stereotype.Service
|
||||||
|
import ru.sicamp.sicamphelper.db.entity.User
|
||||||
|
import ru.sicamp.sicamphelper.repository.UserRepository
|
||||||
|
import ru.sicamp.sicamphelper.util.badArgument
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class UserDetailsServiceImpl(
|
||||||
|
private val userRepository: UserRepository
|
||||||
|
) : UserDetailsService {
|
||||||
|
override fun loadUserByUsername(username: String?): User = userRepository.findUserByUsername(
|
||||||
|
username = username ?: badArgument("Username shouldn't be null")
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,53 +1,32 @@
|
||||||
package ru.sicamp.sicamphelper.service
|
package ru.sicamp.sicamphelper.service
|
||||||
|
|
||||||
import org.jetbrains.exposed.sql.and
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder
|
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
import ru.sicamp.sicamphelper.api.controller.UsersController
|
|
||||||
import ru.sicamp.sicamphelper.db.entity.User
|
import ru.sicamp.sicamphelper.db.entity.User
|
||||||
import ru.sicamp.sicamphelper.db.table.Users
|
|
||||||
import ru.sicamp.sicamphelper.model.dto.UserDto
|
import ru.sicamp.sicamphelper.model.dto.UserDto
|
||||||
import java.time.Duration
|
import ru.sicamp.sicamphelper.model.info.UserInfo
|
||||||
import java.time.LocalDateTime
|
import ru.sicamp.sicamphelper.repository.UserRepository
|
||||||
|
import ru.sicamp.sicamphelper.util.TgUser
|
||||||
|
import ru.sicamp.sicamphelper.util.badArgument
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
class UserService(
|
class UserService(
|
||||||
private val passwordEncoder: PasswordEncoder
|
private val userRepository: UserRepository
|
||||||
) : UserDetailsService {
|
) {
|
||||||
private val userExpirationTime = Duration.ofDays(60)
|
fun findUserByTgUser(user: TgUser): User? = userRepository.findUserByTgUsernameAndId(
|
||||||
private val credentialsExpirationTime = Duration.ofDays(30)
|
id = user.id,
|
||||||
|
userName = user.userName
|
||||||
fun findUserByTgUser(user: org.telegram.telegrambots.meta.api.objects.User): User? = transaction {
|
)
|
||||||
User.find {
|
|
||||||
(Users.tgId eq user.id) and
|
fun findUserInfoByLogin(login: String) = UserInfo.from(
|
||||||
(Users.tgUsername eq user.userName)
|
userRepository.findUserByUsername(login)
|
||||||
}.firstOrNull()
|
) ?: badArgument("Not found user with login=$login")
|
||||||
}
|
|
||||||
|
fun findUserInfoById(id: Long) = UserInfo.from(
|
||||||
|
userRepository.findUserById(id)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun createUser(userDto: UserDto): UserInfo = UserInfo.from(
|
||||||
|
userRepository.createUser(userDto)
|
||||||
|
) ?: error("Failed to create user with login=${userDto.login}")
|
||||||
|
|
||||||
override fun loadUserByUsername(username: String?): User = transaction {
|
|
||||||
username?.let {
|
|
||||||
User.find { Users.login eq it }.firstOrNull()
|
|
||||||
} ?: error("Not found user with username $username")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createUser(userDto: UserDto): User {
|
|
||||||
val now = LocalDateTime.now()
|
|
||||||
UsersController.logger.info("Creating new user at $now")
|
|
||||||
return User.new {
|
|
||||||
login = userDto.login
|
|
||||||
_password = passwordEncoder.encode(userDto.password)
|
|
||||||
role = userDto.role
|
|
||||||
name = userDto.name
|
|
||||||
sex = userDto.sex
|
|
||||||
age = userDto.age
|
|
||||||
tgId = userDto.tgId
|
|
||||||
tgUsername = userDto.tgUsername
|
|
||||||
locked = false
|
|
||||||
enabled = userDto.enabled
|
|
||||||
accountExpiration = now + userExpirationTime
|
|
||||||
credentialsExpiration = now + credentialsExpirationTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
3
src/main/kotlin/ru/sicamp/sicamphelper/util/TgUser.kt
Normal file
3
src/main/kotlin/ru/sicamp/sicamphelper/util/TgUser.kt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
package ru.sicamp.sicamphelper.util
|
||||||
|
|
||||||
|
typealias TgUser = org.telegram.telegrambots.meta.api.objects.User
|
|
@ -2,4 +2,6 @@ package ru.sicamp.sicamphelper.util
|
||||||
|
|
||||||
fun <R, T> R.ifNotNull(obj: T?, block: R.(T) -> R): R = if (obj != null) block(obj) else this
|
fun <R, T> R.ifNotNull(obj: T?, block: R.(T) -> R): R = if (obj != null) block(obj) else this
|
||||||
|
|
||||||
fun <R, T> R.ifNotEmpty(list: List<T>, block: R.(List<T>) -> R): R = if (list.isNotEmpty()) block(list) else this
|
fun <R, T> R.ifNotEmpty(list: List<T>, block: R.(List<T>) -> R): R = if (list.isNotEmpty()) block(list) else this
|
||||||
|
|
||||||
|
fun badArgument(message: String): Nothing = throw IllegalArgumentException(message)
|
Loading…
Reference in a new issue