add auth
This commit is contained in:
parent
2afe7e619d
commit
7762e1de89
11
build.sbt
11
build.sbt
@ -11,6 +11,9 @@ val openapiVersion = "0.11.10"
|
|||||||
val log4catsVersion = "2.6.0"
|
val log4catsVersion = "2.6.0"
|
||||||
val logbackVersion = "1.4.11"
|
val logbackVersion = "1.4.11"
|
||||||
val vaultVersion = "3.6.0"
|
val vaultVersion = "3.6.0"
|
||||||
|
val doobieVersion = "1.0.0-RC5"
|
||||||
|
val postgresqlVersion = "42.7.4"
|
||||||
|
|
||||||
|
|
||||||
lazy val root = (project in file("."))
|
lazy val root = (project in file("."))
|
||||||
.settings(
|
.settings(
|
||||||
@ -35,6 +38,12 @@ lazy val root = (project in file("."))
|
|||||||
"org.typelevel" %% "log4cats-slf4j" % log4catsVersion,
|
"org.typelevel" %% "log4cats-slf4j" % log4catsVersion,
|
||||||
"ch.qos.logback" % "logback-classic" % logbackVersion,
|
"ch.qos.logback" % "logback-classic" % logbackVersion,
|
||||||
|
|
||||||
"org.typelevel" %% "vault" % vaultVersion
|
"org.typelevel" %% "vault" % vaultVersion,
|
||||||
|
|
||||||
|
// Database
|
||||||
|
"org.tpolecat" %% "doobie-core" % doobieVersion,
|
||||||
|
"org.tpolecat" %% "doobie-hikari" % doobieVersion,
|
||||||
|
"org.tpolecat" %% "doobie-postgres" % doobieVersion,
|
||||||
|
"org.postgresql" % "postgresql" % postgresqlVersion
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
139
src/main/resources/db/migration/V1__Initial_schema.sql
Normal file
139
src/main/resources/db/migration/V1__Initial_schema.sql
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION set_updated_at()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
LANGUAGE plpgsql AS
|
||||||
|
$$
|
||||||
|
BEGIN
|
||||||
|
NEW.updated_at := NOW();
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
-- Таблица привилегий (например: "Администратор", "Оператор склада", "Менеджер заказов")
|
||||||
|
CREATE TABLE user_privileges (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
code TEXT NOT NULL UNIQUE, -- internal code (e.g., 'admin', 'warehouse_operator')
|
||||||
|
name TEXT NOT NULL, -- display name
|
||||||
|
description TEXT,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Таблица прав (перечень отдельных действий — CRUD по сущностям)
|
||||||
|
CREATE TABLE user_permissions (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
code TEXT NOT NULL UNIQUE, -- internal code (e.g., 'product.view', 'order.update')
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Связь привилегий и разрешений (многие ко многим)
|
||||||
|
CREATE TABLE user_privilege_permissions (
|
||||||
|
privilege_id INTEGER REFERENCES user_privileges(id) ON DELETE CASCADE,
|
||||||
|
permission_id INTEGER REFERENCES user_permissions(id) ON DELETE CASCADE,
|
||||||
|
PRIMARY KEY (privilege_id, permission_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Таблица юзеров
|
||||||
|
-- CREATE TABLE user_employees (
|
||||||
|
-- id SERIAL PRIMARY KEY,
|
||||||
|
-- full_name TEXT,
|
||||||
|
-- login TEXT UNIQUE,
|
||||||
|
-- password_hash TEXT NOT NULL,
|
||||||
|
-- is_active BOOLEAN DEFAULT FALSE,
|
||||||
|
-- privilege_id INTEGER REFERENCES user_privileges(id),
|
||||||
|
-- created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||||
|
-- );
|
||||||
|
CREATE TABLE users (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
|
||||||
|
full_name TEXT,
|
||||||
|
login TEXT UNIQUE,
|
||||||
|
password_hash TEXT NOT NULL,
|
||||||
|
|
||||||
|
is_blocked BOOLEAN DEFAULT FALSE,
|
||||||
|
is_deleted BOOLEAN DEFAULT FALSE,
|
||||||
|
privilege_id INTEGER REFERENCES user_privileges(id),
|
||||||
|
|
||||||
|
-- Баланс
|
||||||
|
--btc BIGINT DEFAULT 0,
|
||||||
|
--btc_balance NUMERIC(20, 8) DEFAULT 0.0,
|
||||||
|
|
||||||
|
-- Конфиденциальность
|
||||||
|
is_searchable BOOLEAN DEFAULT TRUE,
|
||||||
|
allow_message_forwarding BOOLEAN DEFAULT TRUE,
|
||||||
|
allow_messages_from_non_contacts BOOLEAN DEFAULT TRUE,
|
||||||
|
show_profile_photo_to_non_contacts BOOLEAN DEFAULT TRUE,
|
||||||
|
last_seen_visibility SMALLINT DEFAULT 0 CHECK (last_seen_visibility IN (0, 1, 2)),
|
||||||
|
last_seen_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
-- Биография
|
||||||
|
bio TEXT,
|
||||||
|
show_bio_to_non_contacts BOOLEAN DEFAULT TRUE,
|
||||||
|
|
||||||
|
-- Сторисы
|
||||||
|
show_stories_to_non_contacts BOOLEAN DEFAULT TRUE,
|
||||||
|
|
||||||
|
-- Разрешения на чаты
|
||||||
|
allow_server_chats BOOLEAN DEFAULT TRUE,
|
||||||
|
public_invite_permission SMALLINT DEFAULT 0 CHECK (public_invite_permission IN (0, 1, 2)),
|
||||||
|
group_invite_permission SMALLINT DEFAULT 0 CHECK (group_invite_permission IN (0, 1, 2)),
|
||||||
|
--chat_invite_permission SMALLINT DEFAULT 0 CHECK (chat_invite_permission IN (0, 1, 2)),
|
||||||
|
|
||||||
|
-- Звонки
|
||||||
|
call_permission SMALLINT DEFAULT 0 CHECK (call_permission IN (0, 1, 2)),
|
||||||
|
|
||||||
|
-- Автоудаление аккаунта
|
||||||
|
auto_delete_after_days INTEGER CHECK (auto_delete_after_days IS NULL OR auto_delete_after_days > 0),
|
||||||
|
|
||||||
|
-- Автоудаление сообщений
|
||||||
|
force_auto_delete_messages_in_private BOOLEAN DEFAULT FALSE,
|
||||||
|
max_message_auto_delete_seconds INTEGER CHECK (max_message_auto_delete_seconds IS NULL OR max_message_auto_delete_seconds >= 0),
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- Таблица сессий
|
||||||
|
-- =======================
|
||||||
|
CREATE TABLE user_sessions (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
|
||||||
|
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
|
||||||
|
access_token TEXT NOT NULL, -- JWT
|
||||||
|
refresh_token TEXT NOT NULL, -- JWT
|
||||||
|
|
||||||
|
ip_address TEXT,
|
||||||
|
user_agent TEXT,
|
||||||
|
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
|
||||||
|
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
last_refresh_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- Функция автообновления last_refresh_at при смене токенов
|
||||||
|
-- =======================
|
||||||
|
CREATE OR REPLACE FUNCTION update_last_refresh_at()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
BEGIN
|
||||||
|
IF NEW.access_token IS DISTINCT FROM OLD.access_token THEN
|
||||||
|
NEW.last_refresh_at := CURRENT_TIMESTAMP;
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- =======================
|
||||||
|
-- Триггер на обновление
|
||||||
|
-- =======================
|
||||||
|
CREATE TRIGGER trg_update_last_refresh_at
|
||||||
|
BEFORE UPDATE ON user_sessions
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION update_last_refresh_at();
|
||||||
@ -1,34 +1,41 @@
|
|||||||
package org.yobble.scala_monolith
|
package org.yobble.scala_monolith
|
||||||
|
|
||||||
import cats.effect.{ExitCode, IO, IOApp}
|
import cats.effect.{ExitCode, IO, IOApp, Resource}
|
||||||
import org.http4s.ember.server.EmberServerBuilder
|
import org.http4s.ember.server.EmberServerBuilder
|
||||||
import org.http4s.server.Router
|
import org.http4s.server.Router
|
||||||
import com.comcast.ip4s._
|
import com.comcast.ip4s._
|
||||||
import org.yobble.scala_monolith.api.route.Routes
|
import org.yobble.scala_monolith.api.route.Routes
|
||||||
|
import org.yobble.scala_monolith.config.{Database, DatabaseConfig}
|
||||||
import org.yobble.scala_monolith.middleware.GlobalErrorHandler
|
import org.yobble.scala_monolith.middleware.GlobalErrorHandler
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
|
import doobie.hikari.HikariTransactor
|
||||||
|
|
||||||
object Main extends IOApp {
|
object Main extends IOApp {
|
||||||
override def run(args: List[String]): IO[ExitCode] = {
|
override def run(args: List[String]): IO[ExitCode] = {
|
||||||
val httpApp = Router("/" -> Routes.all).orNotFound
|
val resources: Resource[IO, HikariTransactor[IO]] = for {
|
||||||
val httpAppWithMiddleware = GlobalErrorHandler.withGlobalErrorHandler(httpApp)
|
dbConfig <- Resource.eval(DatabaseConfig.load[IO])
|
||||||
|
transactor <- Database.transactor[IO](dbConfig)
|
||||||
|
} yield transactor
|
||||||
|
|
||||||
val port = sys.env.get("HTTP_PORT").flatMap(_.toIntOption).getOrElse(8080)
|
resources.use { transactor =>
|
||||||
|
val httpApp = Router("/" -> Routes.all(transactor)).orNotFound
|
||||||
|
val httpAppWithMiddleware = GlobalErrorHandler.withGlobalErrorHandler(httpApp)
|
||||||
|
|
||||||
EmberServerBuilder
|
val port = sys.env.get("HTTP_PORT").flatMap(_.toIntOption).getOrElse(8080)
|
||||||
.default[IO]
|
|
||||||
.withHost(ipv4"0.0.0.0")
|
EmberServerBuilder
|
||||||
.withPort(Port.fromInt(port).get)
|
.default[IO]
|
||||||
.withHttpApp(httpAppWithMiddleware)
|
.withHost(ipv4"0.0.0.0")
|
||||||
.build
|
.withPort(Port.fromInt(port).get)
|
||||||
.use { server =>
|
.withHttpApp(httpAppWithMiddleware)
|
||||||
// Здесь вручную выводим "localhost" в лог, хотя слушаем "0.0.0.0"
|
.build
|
||||||
for {
|
.use { server =>
|
||||||
_ <- IO.println(s"Server running at http://localhost:$port")
|
for {
|
||||||
_ <- IO.println(s"Swagger UI available at http://localhost:$port/docs")
|
_ <- IO.println(s"Server running at http://localhost:$port")
|
||||||
_ <- IO.never
|
_ <- IO.println(s"Swagger UI available at http://localhost:$port/docs")
|
||||||
} yield ()
|
_ <- IO.never
|
||||||
}
|
} yield ()
|
||||||
.as(ExitCode.Success)
|
}
|
||||||
|
}.as(ExitCode.Success)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
package org.yobble.scala_monolith.api.dto
|
||||||
|
|
||||||
|
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
|
||||||
|
import io.circe.{Decoder, Encoder}
|
||||||
|
|
||||||
|
case class LoginRequest(login: String, password: String)
|
||||||
|
|
||||||
|
object LoginRequest {
|
||||||
|
implicit val decoder: Decoder[LoginRequest] = deriveDecoder[LoginRequest]
|
||||||
|
implicit val encoder: Encoder[LoginRequest] = deriveEncoder[LoginRequest]
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package org.yobble.scala_monolith.api.dto
|
||||||
|
|
||||||
|
import io.circe.generic.semiauto.{deriveDecoder, deriveEncoder}
|
||||||
|
import io.circe.{Decoder, Encoder}
|
||||||
|
|
||||||
|
case class LoginResponse(accessToken: String, refreshToken: String)
|
||||||
|
|
||||||
|
object LoginResponse {
|
||||||
|
implicit val decoder: Decoder[LoginResponse] = deriveDecoder[LoginResponse]
|
||||||
|
implicit val encoder: Encoder[LoginResponse] = deriveEncoder[LoginResponse]
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
package org.yobble.scala_monolith.api.endpoint.auth
|
||||||
|
|
||||||
|
import org.yobble.scala_monolith.api.dto.{LoginRequest, LoginResponse}
|
||||||
|
import sttp.tapir._
|
||||||
|
import sttp.tapir.generic.auto._
|
||||||
|
import sttp.tapir.json.circe._
|
||||||
|
|
||||||
|
object AuthEndpoints {
|
||||||
|
|
||||||
|
val loginEndpoint: PublicEndpoint[LoginRequest, String, LoginResponse, Any] =
|
||||||
|
endpoint.post
|
||||||
|
.in("auth" / "login")
|
||||||
|
.in(jsonBody[LoginRequest])
|
||||||
|
.out(jsonBody[LoginResponse])
|
||||||
|
.errorOut(stringBody)
|
||||||
|
}
|
||||||
@ -1,14 +1,29 @@
|
|||||||
package org.yobble.scala_monolith.api.route
|
package org.yobble.scala_monolith.api.route
|
||||||
|
|
||||||
import cats.effect.IO
|
import cats.effect.IO
|
||||||
|
import doobie.Transactor
|
||||||
|
import org.yobble.scala_monolith.api.endpoint.auth.AuthEndpoints
|
||||||
import sttp.tapir.server.ServerEndpoint
|
import sttp.tapir.server.ServerEndpoint
|
||||||
import org.yobble.scala_monolith.api.endpoint.PingEndpoint
|
import org.yobble.scala_monolith.api.endpoint.PingEndpoint
|
||||||
import org.yobble.scala_monolith.api.endpoint.ErrorsEndpoint
|
import org.yobble.scala_monolith.api.endpoint.ErrorsEndpoint
|
||||||
import org.yobble.scala_monolith.api.response.BaseResponse
|
import org.yobble.scala_monolith.api.response.BaseResponse
|
||||||
|
import org.yobble.scala_monolith.repository.UserRepositoryImpl
|
||||||
|
import org.yobble.scala_monolith.service.AuthService
|
||||||
|
|
||||||
object AllServerEndpoints {
|
object AllServerEndpoints {
|
||||||
val all: List[ServerEndpoint[Any, IO]] = List(
|
def all(transactor: Transactor[IO]): List[ServerEndpoint[Any, IO]] = {
|
||||||
PingEndpoint.pingEndpoint.serverLogicSuccess(_ => IO.pure(BaseResponse(message = "pong"))),
|
|
||||||
ErrorsEndpoint.errorsEndpoint.serverLogicSuccess(_ => IO.pure(BaseResponse(message = "errors")))
|
val authService = new AuthService(new UserRepositoryImpl(transactor))
|
||||||
)
|
|
||||||
}
|
val authEndpoints = List(
|
||||||
|
AuthEndpoints.loginEndpoint.serverLogic(authService.login)
|
||||||
|
)
|
||||||
|
|
||||||
|
val otherEndpoints = List(
|
||||||
|
PingEndpoint.pingEndpoint.serverLogicSuccess(_ => IO.pure(BaseResponse(message = "pong"))),
|
||||||
|
ErrorsEndpoint.errorsEndpoint.serverLogicSuccess(_ => IO.pure(BaseResponse(message = "errors")))
|
||||||
|
)
|
||||||
|
|
||||||
|
authEndpoints ++ otherEndpoints
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package org.yobble.scala_monolith.api.route
|
package org.yobble.scala_monolith.api.route
|
||||||
|
|
||||||
import cats.effect.IO
|
import cats.effect.IO
|
||||||
|
import doobie.Transactor
|
||||||
import org.http4s.HttpRoutes
|
import org.http4s.HttpRoutes
|
||||||
import sttp.tapir.server.http4s.Http4sServerInterpreter
|
import sttp.tapir.server.http4s.Http4sServerInterpreter
|
||||||
import sttp.tapir.swagger.bundle.SwaggerInterpreter
|
import sttp.tapir.swagger.bundle.SwaggerInterpreter
|
||||||
@ -8,17 +9,19 @@ import org.yobble.scala_monolith.middleware.RealIpAndUserAgentMiddleware
|
|||||||
import cats.syntax.semigroupk._
|
import cats.syntax.semigroupk._
|
||||||
|
|
||||||
object Routes {
|
object Routes {
|
||||||
private val allServerEndpoints = AllServerEndpoints.all
|
def all(transactor: Transactor[IO]): HttpRoutes[IO] = {
|
||||||
|
val allServerEndpoints = AllServerEndpoints.all(transactor)
|
||||||
|
|
||||||
private val httpRoutes = Http4sServerInterpreter[IO]().toRoutes(allServerEndpoints)
|
val httpRoutes = Http4sServerInterpreter[IO]().toRoutes(allServerEndpoints)
|
||||||
|
|
||||||
private val docsRoutes = Http4sServerInterpreter[IO]().toRoutes(
|
val docsRoutes = Http4sServerInterpreter[IO]().toRoutes(
|
||||||
SwaggerInterpreter().fromServerEndpoints[IO](
|
SwaggerInterpreter().fromServerEndpoints[IO](
|
||||||
allServerEndpoints,
|
allServerEndpoints,
|
||||||
"scala_monolith API",
|
"scala_monolith API",
|
||||||
"1.0"
|
"1.0"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
val all: HttpRoutes[IO] = RealIpAndUserAgentMiddleware(docsRoutes <+> httpRoutes)
|
RealIpAndUserAgentMiddleware(docsRoutes <+> httpRoutes)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
package org.yobble.scala_monolith.config
|
||||||
|
|
||||||
|
import cats.effect.{Async, Resource}
|
||||||
|
import doobie.hikari.HikariTransactor
|
||||||
|
import org.yobble.scala_monolith.config.DatabaseConfig
|
||||||
|
import scala.concurrent.ExecutionContext
|
||||||
|
|
||||||
|
object Database {
|
||||||
|
|
||||||
|
def transactor[F[_]: Async](config: DatabaseConfig): Resource[F, HikariTransactor[F]] = {
|
||||||
|
for {
|
||||||
|
ec <- Resource.eval(Async[F].executionContext)
|
||||||
|
transactor <- HikariTransactor.newHikariTransactor[F](
|
||||||
|
driverClassName = "org.postgresql.Driver",
|
||||||
|
url = config.url,
|
||||||
|
user = config.user,
|
||||||
|
pass = config.password,
|
||||||
|
connectEC = ec
|
||||||
|
)
|
||||||
|
} yield transactor
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
package org.yobble.scala_monolith.config
|
||||||
|
|
||||||
|
import cats.effect.Sync
|
||||||
|
|
||||||
|
case class DatabaseConfig(
|
||||||
|
url: String,
|
||||||
|
user: String,
|
||||||
|
password: String
|
||||||
|
)
|
||||||
|
|
||||||
|
object DatabaseConfig {
|
||||||
|
def load[F[_]: Sync]: F[DatabaseConfig] = {
|
||||||
|
Sync[F].delay {
|
||||||
|
// TODO: Load from a proper config file (e.g., application.conf)
|
||||||
|
DatabaseConfig(
|
||||||
|
url = "jdbc:postgresql://localhost:5101/yobble_db",
|
||||||
|
user = "yobble_app_user",
|
||||||
|
password = "strong_password_here"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/main/scala/org/yobble/scala_monolith/models/User.scala
Normal file
11
src/main/scala/org/yobble/scala_monolith/models/User.scala
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package org.yobble.scala_monolith.models
|
||||||
|
|
||||||
|
import doobie.Read
|
||||||
|
import doobie.util.meta.Meta
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
case class User(id: UUID, login: String, passwordHash: String) derives Read
|
||||||
|
|
||||||
|
object User {
|
||||||
|
implicit val uuidMeta: Meta[UUID] = Meta[String].timap(UUID.fromString)(_.toString)
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package org.yobble.scala_monolith.repository
|
||||||
|
|
||||||
|
import cats.effect.IO
|
||||||
|
import doobie.implicits._
|
||||||
|
import doobie.postgres.implicits._
|
||||||
|
import doobie.Transactor
|
||||||
|
import org.yobble.scala_monolith.models.User
|
||||||
|
|
||||||
|
trait UserRepository {
|
||||||
|
def findByLogin(login: String): IO[Option[User]]
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserRepositoryImpl(transactor: Transactor[IO]) extends UserRepository {
|
||||||
|
override def findByLogin(login: String): IO[Option[User]] = {
|
||||||
|
sql"SELECT id, login, password_hash as passwordHash FROM users WHERE login = $login"
|
||||||
|
.query[User]
|
||||||
|
.option
|
||||||
|
.transact(transactor)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
package org.yobble.scala_monolith.service
|
||||||
|
|
||||||
|
import cats.effect.IO
|
||||||
|
import org.yobble.scala_monolith.api.dto.{LoginRequest, LoginResponse}
|
||||||
|
import org.yobble.scala_monolith.repository.UserRepository
|
||||||
|
|
||||||
|
class AuthService(userRepository: UserRepository) {
|
||||||
|
|
||||||
|
def login(request: LoginRequest): IO[Either[String, LoginResponse]] = {
|
||||||
|
userRepository.findByLogin(request.login).map {
|
||||||
|
case Some(user) if user.passwordHash == request.password =>
|
||||||
|
// TODO: Implement proper password hashing (e.g., with bcrypt)
|
||||||
|
// TODO: Implement real token generation
|
||||||
|
Right(LoginResponse(accessToken = "fake-access-token", refreshToken = "fake-refresh-token"))
|
||||||
|
case _ =>
|
||||||
|
Left("Invalid login or password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user