Skip to content

Commit

Permalink
Push works
Browse files Browse the repository at this point in the history
  • Loading branch information
cheroliv committed Sep 28, 2024
1 parent e6a7df9 commit d38486b
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 186 deletions.
4 changes: 2 additions & 2 deletions documentation/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

project = 'Documentation'
copyright = '2024, @prologeek'
author = '@prologeek'
copyright = '2024, @cheroliv'
author = '@cheroliv'
release = '0.0.1-SNAPSHOT'

# -- General configuration ---------------------------------------------------
Expand Down
252 changes: 148 additions & 104 deletions springboot/api/src/main/kotlin/webapp/users/User.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@file:Suppress(
"RemoveRedundantQualifierName",
"MemberVisibilityCanBePrivate", "SqlNoDataSourceInspection"
"RemoveRedundantQualifierName",
"MemberVisibilityCanBePrivate", "SqlNoDataSourceInspection"
)

package webapp.users
Expand All @@ -16,6 +16,8 @@ import jakarta.validation.constraints.Size
import org.springframework.beans.factory.getBean
import org.springframework.context.ApplicationContext
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate
import org.springframework.r2dbc.core.DatabaseClient
import org.springframework.r2dbc.core.awaitOne
import org.springframework.r2dbc.core.awaitRowsUpdated
import webapp.core.property.ANONYMOUS_USER
import webapp.core.property.EMPTY_STRING
Expand All @@ -39,67 +41,84 @@ import webapp.users.security.UserRole.UserRoleDao
import java.util.*
import jakarta.validation.constraints.Email as EmailConstraint

abstract class EntityModel<T>(
// Generic ID, which can be of any type
open val id: T? = null
)

// Generic extension function that allows the ID to be applied to any EntityModel type
inline fun <reified T : EntityModel<ID>, ID> T.withId(id: ID): T {
// Use reflection to create a copy with the passed ID
return this::class.constructors
.first { it.parameters.any { param -> param.name == "id" || param.name == "role"} }
.call(id, *this::class.constructors.first().parameters.drop(1).map { param ->
this::class.members.first { member -> member.name == param.name }.call(this)
}.toTypedArray())
}

data class User(
val id: UUID? = null,
@field:NotNull
@field:Pattern(regexp = LOGIN_REGEX)
@field:Size(min = 1, max = 50)
val login: String,
@JsonIgnore
@field:NotNull
@field:Size(min = 60, max = 60)
val password: String = EMPTY_STRING,
@field:EmailConstraint
@field:Size(min = 5, max = 254)
val email: String = EMPTY_STRING,
@JsonIgnore
val roles: MutableSet<Role> = mutableSetOf(Role(ANONYMOUS_USER)),
@field:Size(min = 2, max = 10)
val langKey: String = EMPTY_STRING,
@JsonIgnore
val version: Long = -1,
) {
companion object {
@JvmStatic
fun main(args: Array<String>) = println(UserDao.Relations.sqlScript)
}

object UserDao {
object Constraints {
// Regex for acceptable logins
const val LOGIN_REGEX =
"^(?>[a-zA-Z0-9!$&*+=?^_`{|}~.-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)|(?>[_.@A-Za-z0-9-]+)$"
const val PASSWORD_MIN: Int = 4
const val PASSWORD_MAX: Int = 16
const val IMAGE_URL_DEFAULT = "https://placehold.it/50x50"
}
override val id: UUID? = null,
@field:NotNull
@field:Pattern(regexp = LOGIN_REGEX)
@field:Size(min = 1, max = 50)
val login: String,
@JsonIgnore
@field:NotNull
@field:Size(min = 60, max = 60)
val password: String = EMPTY_STRING,
@field:EmailConstraint
@field:Size(min = 5, max = 254)
val email: String = EMPTY_STRING,
@JsonIgnore
val roles: MutableSet<Role> = mutableSetOf(Role(ANONYMOUS_USER)),
@field:Size(min = 2, max = 10)
val langKey: String = EMPTY_STRING,
@JsonIgnore
val version: Long = -1,
) : EntityModel<UUID>() {

object Members {
const val PASSWORD_MEMBER = "password"
const val ROLES_MEMBER = "roles"
}

object Fields {
const val ID_FIELD = "`id`"
const val LOGIN_FIELD = "`login`"
const val PASSWORD_FIELD = "`password`"
const val EMAIL_FIELD = "`email`"
const val LANG_KEY_FIELD = "`lang_key`"
const val VERSION_FIELD = "`version`"
companion object {
@JvmStatic
fun main(args: Array<String>) = println(UserDao.Relations.sqlScript)
}

object Attributes {
val ID_ATTR = ID_FIELD.cleanField()
val LOGIN_ATTR = LOGIN_FIELD.cleanField()
val PASSWORD_ATTR = PASSWORD_FIELD.cleanField()
val EMAIL_ATTR = EMAIL_FIELD.cleanField()
const val LANG_KEY_ATTR = "langKey"
val VERSION_ATTR = VERSION_FIELD.cleanField()
}
object UserDao {
object Constraints {
// Regex for acceptable logins
const val LOGIN_REGEX =
"^(?>[a-zA-Z0-9!$&*+=?^_`{|}~.-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)|(?>[_.@A-Za-z0-9-]+)$"
const val PASSWORD_MIN: Int = 4
const val PASSWORD_MAX: Int = 16
const val IMAGE_URL_DEFAULT = "https://placehold.it/50x50"
}

object Members {
const val PASSWORD_MEMBER = "password"
const val ROLES_MEMBER = "roles"
}

object Relations {
const val TABLE_NAME = "`user`"
const val SQL_SCRIPT = """
object Fields {
const val ID_FIELD = "`id`"
const val LOGIN_FIELD = "`login`"
const val PASSWORD_FIELD = "`password`"
const val EMAIL_FIELD = "`email`"
const val LANG_KEY_FIELD = "`lang_key`"
const val VERSION_FIELD = "`version`"
}

object Attributes {
val ID_ATTR = ID_FIELD.cleanField()
val LOGIN_ATTR = LOGIN_FIELD.cleanField()
val PASSWORD_ATTR = PASSWORD_FIELD.cleanField()
val EMAIL_ATTR = EMAIL_FIELD.cleanField()
const val LANG_KEY_ATTR = "langKey"
val VERSION_ATTR = VERSION_FIELD.cleanField()
}

object Relations {
const val TABLE_NAME = "`user`"
const val SQL_SCRIPT = """
CREATE TABLE IF NOT EXISTS $TABLE_NAME (
$ID_FIELD UUID default random_uuid() PRIMARY KEY,
$LOGIN_FIELD VARCHAR,
Expand All @@ -113,62 +132,87 @@ data class User(
CREATE UNIQUE INDEX IF NOT EXISTS `uniq_idx_user_email`
ON $TABLE_NAME ($EMAIL_FIELD);
"""
@Suppress("SqlDialectInspection")
const val INSERT = """

@Suppress("SqlDialectInspection")
const val INSERT = """
insert into $TABLE_NAME (
$LOGIN_FIELD, $EMAIL_FIELD,
$PASSWORD_FIELD, $LANG_KEY_FIELD,
$VERSION_FIELD
) values ( :login, :email, :password, :langKey, :version)"""

@JvmStatic
val sqlScript: String
get() = setOf(
UserDao.Relations.SQL_SCRIPT,
RoleDao.Relations.SQL_SCRIPT,
UserRoleDao.Relations.SQL_SCRIPT
).joinToString("")
.trimMargin()
@JvmStatic
val sqlScript: String
get() = setOf(
UserDao.Relations.SQL_SCRIPT,
RoleDao.Relations.SQL_SCRIPT,
UserRoleDao.Relations.SQL_SCRIPT
).joinToString("")
.trimMargin()


//TODO: signup, findByEmailOrLogin,
}

object Dao {
val Pair<User, ApplicationContext>.toJson: String
get() = second.getBean<ObjectMapper>().writeValueAsString(first)

//TODO: signup, findByEmailOrLogin,
suspend fun Pair<User, ApplicationContext>.save(): Either<Throwable, Long> = try {
second
.getBean<R2dbcEntityTemplate>()
.databaseClient
.sql(INSERT)
.bind(LOGIN_ATTR, first.login)
.bind(EMAIL_ATTR, first.email)
.bind(PASSWORD_ATTR, first.password)
.bind(LANG_KEY_ATTR, first.langKey)
.bind(VERSION_ATTR, first.version)
.fetch()
.awaitRowsUpdated()
.right()
} catch (e: Throwable) {
e.left()
}


suspend fun Pair<User, ApplicationContext>.findOneByEmail(email: String): Either<Throwable, User> = try {
second
.getBean<DatabaseClient>()
.sql("SELECT * FROM `user` WHERE LOWER(email) = LOWER(:email)")
.bind("email", email)
.fetch()
.awaitOne()
.let { row ->
User(
id = row[User.UserDao.Attributes.ID_ATTR] as UUID?,
login = row[User.UserDao.Attributes.LOGIN_ATTR] as String,
password = row[User.UserDao.Attributes.PASSWORD_ATTR] as String,
email = row[User.UserDao.Attributes.EMAIL_ATTR] as String,
langKey = row[User.UserDao.Attributes.LANG_KEY_ATTR] as String,
version = row[User.UserDao.Attributes.VERSION_ATTR] as Long
)
}
.right()
} catch (e: Throwable) {
e.left()
}
}
}
object Dao {
val Pair<User, ApplicationContext>.toJson: String
get() = second.getBean<ObjectMapper>().writeValueAsString(first)

suspend fun Pair<User, ApplicationContext>.save(): Either<Throwable, Long> = try {
second
.getBean<R2dbcEntityTemplate>()
.databaseClient
.sql(INSERT)
.bind(LOGIN_ATTR, first.login)
.bind(EMAIL_ATTR, first.email)
.bind(PASSWORD_ATTR, first.password)
.bind(LANG_KEY_ATTR, first.langKey)
.bind(VERSION_ATTR, first.version)
.fetch()
.awaitRowsUpdated()
.right()
} catch (e: Throwable) {
e.left()
}

/** Account REST API URIs */
object UserRestApis {
const val API_AUTHORITY = "/api/authorities"
const val API_USERS = "/api/users"
const val API_SIGNUP = "/signup"
const val API_SIGNUP_PATH = "$API_USERS$API_SIGNUP"
const val API_ACTIVATE = "/activate"
const val API_ACTIVATE_PATH = "$API_USERS$API_ACTIVATE?key="
const val API_ACTIVATE_PARAM = "{activationKey}"
const val API_ACTIVATE_KEY = "key"
const val API_RESET_INIT = "/reset-password/init"
const val API_RESET_FINISH = "/reset-password/finish"
const val API_CHANGE = "/change-password"
const val API_CHANGE_PATH = "$API_USERS$API_CHANGE"
}
}
/** Account REST API URIs */
object UserRestApis {
const val API_AUTHORITY = "/api/authorities"
const val API_USERS = "/api/users"
const val API_SIGNUP = "/signup"
const val API_SIGNUP_PATH = "$API_USERS$API_SIGNUP"
const val API_ACTIVATE = "/activate"
const val API_ACTIVATE_PATH = "$API_USERS$API_ACTIVATE?key="
const val API_ACTIVATE_PARAM = "{activationKey}"
const val API_ACTIVATE_KEY = "key"
const val API_RESET_INIT = "/reset-password/init"
const val API_RESET_FINISH = "/reset-password/finish"
const val API_CHANGE = "/change-password"
const val API_CHANGE_PATH = "$API_USERS$API_CHANGE"
}
}
10 changes: 4 additions & 6 deletions springboot/api/src/test/kotlin/webapp/ApplicationTests.kt
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package webapp

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.getBean
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.context.ApplicationContext
import org.springframework.context.MessageSource
import org.springframework.test.context.ActiveProfiles
import webapp.TestUtils.Data.OFFICIAL_SITE
import webapp.core.property.Config
import webapp.core.property.DEVELOPMENT
import webapp.core.property.PRODUCTION
import webapp.core.property.STARTUP_LOG_MSG_KEY
import webapp.core.utils.i
import webapp.tests.TestUtils.Data.OFFICIAL_SITE
import java.util.*
import java.util.Locale.FRENCH
import javax.inject.Inject
import kotlin.test.Test
import kotlin.test.assertEquals

Expand All @@ -22,7 +22,7 @@ import kotlin.test.assertEquals
@ActiveProfiles("test")
class ApplicationTests {

@Inject
@Autowired
lateinit var context: ApplicationContext

@Test
Expand Down Expand Up @@ -64,8 +64,6 @@ class ApplicationTests {
fun `ConfigurationsTests - test go visit message`() =
assertEquals(
OFFICIAL_SITE,
context
.getBean<Config>()
.goVisitMessage
context.getBean<Config>().goVisitMessage
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
@file:Suppress("unused")

package webapp.tests
package webapp


//import webapp.accounts.models.Account
Expand All @@ -18,7 +18,6 @@ import org.hamcrest.TypeSafeDiagnosingMatcher
import org.springframework.boot.runApplication
import org.springframework.boot.web.reactive.context.StandardReactiveWebEnvironment
import org.springframework.context.ConfigurableApplicationContext
import webapp.Application
import webapp.core.property.*
import webapp.core.utils.i
import java.io.IOException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"unused"
)

package webapp.tests
package webapp

import org.springframework.beans.factory.getBean
import org.springframework.context.ApplicationContext
Expand Down Expand Up @@ -59,6 +59,9 @@ object TestUtils {
val ApplicationContext.defaultRoles
get() = setOf(ROLE_ADMIN, ROLE_USER, ROLE_ANONYMOUS)




suspend fun ApplicationContext.countUsers(): Int =
"select count(*) from `user`"
.let(getBean<DatabaseClient>()::sql)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//@file:Suppress(
package webapp.users//@file:Suppress(
// "NonAsciiCharacters",
// "MemberVisibilityCanBePrivate",
// "PropertyName"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//@file:Suppress("NonAsciiCharacters")
//
package webapp.users
//package webapp.repository
//
//import kotlinx.coroutines.runBlocking
Expand Down
Loading

0 comments on commit d38486b

Please sign in to comment.