Compare commits

..

2 commits

Author SHA1 Message Date
638c360975 Fixed spring configuration 2023-08-11 13:15:29 +04:00
140edcbb6d UserInfo receiving implemented 2023-08-09 14:42:44 +04:00
12 changed files with 198 additions and 85 deletions

View file

@ -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")

View file

@ -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) {

View file

@ -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()
} }

View file

@ -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)
} }
} }

View file

@ -0,0 +1,7 @@
package ru.sicamp.sicamphelper.model.info
data class EntityRef(
val type: Info.Type,
val id: Long,
val name: String
)

View 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
}
}

View file

@ -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
)
}
}
}

View file

@ -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)
}
}

View file

@ -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")
)
}

View file

@ -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
}
}
} }

View file

@ -0,0 +1,3 @@
package ru.sicamp.sicamphelper.util
typealias TgUser = org.telegram.telegrambots.meta.api.objects.User

View file

@ -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)