module IHP.ValidationSupport.ValidateIsUnique (validateIsUnique) where

import IHP.Prelude
import Database.PostgreSQL.Simple.ToField
import IHP.ModelSupport
import IHP.ValidationSupport.Types
import IHP.HaskellSupport
import IHP.QueryBuilder
import IHP.Fetch

-- | Validates that e.g. an email (or another field) is unique across all users before inserting.
--
-- This validator reads the given field name (e.g. email) from the record, and runs a database query
-- to check that there is no other record using the same field value (e.g. email value).
--
-- __Example:__ Validate that an email is unique
--
-- > action CreateUserAction = do
-- >     let user = newRecord @User
-- >     user
-- >         |> fill @'["email"]
-- >         |> validateIsUnique #email
-- >         >>= ifValid \case
-- >             Left user -> render NewView { .. } 
-- >             Right user -> do
-- >                 createRecord user
-- >                 redirectTo UsersAction
validateIsUnique :: forall field model savedModel validationState fieldValue validationStateValue fetchedModel modelId savedModelId. (
        savedModel ~ NormalizeModel model
        , ?modelContext :: ModelContext
        , FromRow savedModel
        , KnownSymbol field
        , HasField field model fieldValue
        , HasField field savedModel fieldValue
        , KnownSymbol (GetTableName savedModel)
        , ToField fieldValue
        , EqOrIsOperator fieldValue
        , HasField "meta" model MetaBag
        , SetField "meta" model MetaBag
        , HasField "id" savedModel savedModelId
        , HasField "id" model modelId
        , savedModelId ~ modelId
        , Eq modelId
        , GetModelByTableName (GetTableName savedModel) ~ savedModel
    ) => Proxy field -> model -> IO model
validateIsUnique :: Proxy field -> model -> IO model
validateIsUnique Proxy field
fieldProxy model
model = do
    let value :: fieldValue
value = model -> fieldValue
forall k (x :: k) r a. HasField x r a => r -> a
getField @field model
model
    Maybe savedModel
result <- forall model (table :: Symbol).
(table ~ GetTableName model, DefaultScope table) =>
QueryBuilder table
forall (table :: Symbol).
(table ~ GetTableName savedModel, DefaultScope table) =>
QueryBuilder table
query @savedModel
        QueryBuilder (GetTableName savedModel)
-> (QueryBuilder (GetTableName savedModel)
    -> QueryBuilder (GetTableName savedModel))
-> QueryBuilder (GetTableName savedModel)
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> (Proxy field, fieldValue)
-> QueryBuilder (GetTableName savedModel)
-> QueryBuilder (GetTableName savedModel)
forall (name :: Symbol) (table :: Symbol) model value.
(KnownSymbol name, ToField value, HasField name model value,
 EqOrIsOperator value, model ~ GetModelByTableName table) =>
(Proxy name, value) -> QueryBuilder table -> QueryBuilder table
filterWhere (Proxy field
fieldProxy, fieldValue
value)
        QueryBuilder (GetTableName savedModel)
-> (QueryBuilder (GetTableName savedModel)
    -> IO (Maybe savedModel))
-> IO (Maybe savedModel)
forall t1 t2. t1 -> (t1 -> t2) -> t2
|> QueryBuilder (GetTableName savedModel) -> IO (Maybe savedModel)
forall fetchable model.
(Fetchable fetchable model, KnownSymbol (GetTableName model),
 FromRow model, ?modelContext::ModelContext) =>
fetchable -> IO (Maybe model)
fetchOneOrNothing
    case Maybe savedModel
result of
        Just savedModel
value | Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (model -> modelId
forall k (x :: k) r a. HasField x r a => r -> a
getField @"id" model
model) modelId -> modelId -> Bool
forall a. Eq a => a -> a -> Bool
== (savedModel -> modelId
forall k (x :: k) r a. HasField x r a => r -> a
getField @"id" savedModel
value) -> model -> IO model
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Proxy field -> ValidatorResult -> model -> model
forall (field :: Symbol) model.
(KnownSymbol field, HasField "meta" model MetaBag,
 SetField "meta" model MetaBag) =>
Proxy field -> ValidatorResult -> model -> model
attachValidatorResult Proxy field
fieldProxy (Text -> ValidatorResult
Failure Text
"This is already in use") model
model)
        Maybe savedModel
_ -> model -> IO model
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Proxy field -> ValidatorResult -> model -> model
forall (field :: Symbol) model.
(KnownSymbol field, HasField "meta" model MetaBag,
 SetField "meta" model MetaBag) =>
Proxy field -> ValidatorResult -> model -> model
attachValidatorResult Proxy field
fieldProxy ValidatorResult
Success model
model)
{-# INLINE validateIsUnique #-}