module IHP.IDE.CodeGen.JobGenerator (buildPlan, buildPlan', JobConfig (..)) where

import IHP.Prelude
import qualified Data.Text as Text
import IHP.IDE.CodeGen.Types
import qualified System.Directory as Directory

data JobConfig = JobConfig
    { JobConfig -> Text
applicationName :: Text
    , JobConfig -> Text
tableName :: Text -- E.g. create_container_jobs
    , JobConfig -> Text
modelName :: Text -- E.g. CreateContainerJob
    , JobConfig -> Bool
isFirstJobInApplication :: Bool -- If true, creates Worker.hs in application directory
    } deriving (JobConfig -> JobConfig -> Bool
(JobConfig -> JobConfig -> Bool)
-> (JobConfig -> JobConfig -> Bool) -> Eq JobConfig
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: JobConfig -> JobConfig -> Bool
$c/= :: JobConfig -> JobConfig -> Bool
== :: JobConfig -> JobConfig -> Bool
$c== :: JobConfig -> JobConfig -> Bool
Eq, Int -> JobConfig -> ShowS
[JobConfig] -> ShowS
JobConfig -> String
(Int -> JobConfig -> ShowS)
-> (JobConfig -> String)
-> ([JobConfig] -> ShowS)
-> Show JobConfig
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [JobConfig] -> ShowS
$cshowList :: [JobConfig] -> ShowS
show :: JobConfig -> String
$cshow :: JobConfig -> String
showsPrec :: Int -> JobConfig -> ShowS
$cshowsPrec :: Int -> JobConfig -> ShowS
Show)

buildPlan :: Text -> Text -> IO (Either Text [GeneratorAction])
buildPlan :: Text -> Text -> IO (Either Text [GeneratorAction])
buildPlan Text
jobName Text
applicationName = do
    Bool
isFirstJobInApplication <- Bool -> Bool
not (Bool -> Bool) -> IO Bool -> IO Bool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> IO Bool
Directory.doesFileExist (Text -> String
forall a b. ConvertibleStrings a b => a -> b
cs (Text -> String) -> Text -> String
forall a b. (a -> b) -> a -> b
$ Text
applicationName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/Worker.hs")
    if Text -> Bool
forall mono. MonoFoldable mono => mono -> Bool
null Text
jobName
        then Either Text [GeneratorAction] -> IO (Either Text [GeneratorAction])
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either Text [GeneratorAction]
 -> IO (Either Text [GeneratorAction]))
-> Either Text [GeneratorAction]
-> IO (Either Text [GeneratorAction])
forall a b. (a -> b) -> a -> b
$ Text -> Either Text [GeneratorAction]
forall a b. a -> Either a b
Left Text
"Job name cannot be empty"
        else do
            let jobConfig :: JobConfig
jobConfig = JobConfig :: Text -> Text -> Text -> Bool -> JobConfig
JobConfig
                    { Text
applicationName :: Text
$sel:applicationName:JobConfig :: Text
applicationName
                    , $sel:tableName:JobConfig :: Text
tableName = Text
jobName
                    , $sel:modelName:JobConfig :: Text
modelName = Text -> Text
tableNameToModelName Text
jobName
                    , Bool
isFirstJobInApplication :: Bool
$sel:isFirstJobInApplication:JobConfig :: Bool
isFirstJobInApplication
                    }
            Either Text [GeneratorAction] -> IO (Either Text [GeneratorAction])
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Either Text [GeneratorAction]
 -> IO (Either Text [GeneratorAction]))
-> Either Text [GeneratorAction]
-> IO (Either Text [GeneratorAction])
forall a b. (a -> b) -> a -> b
$ [GeneratorAction] -> Either Text [GeneratorAction]
forall a b. b -> Either a b
Right ([GeneratorAction] -> Either Text [GeneratorAction])
-> [GeneratorAction] -> Either Text [GeneratorAction]
forall a b. (a -> b) -> a -> b
$ JobConfig -> [GeneratorAction]
buildPlan' JobConfig
jobConfig

-- E.g. qualifiedMailModuleName config "Confirmation" == "Web.Mail.Users.Confirmation"
qualifiedJobModuleName :: JobConfig -> Text
qualifiedJobModuleName :: JobConfig -> Text
qualifiedJobModuleName JobConfig
config =
    Proxy "applicationName" -> JobConfig -> Text
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "applicationName" (Proxy "applicationName")
Proxy "applicationName"
#applicationName JobConfig
config Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
".Job." Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> JobConfig -> Text
unqualifiedJobModuleName JobConfig
config

unqualifiedJobModuleName :: JobConfig -> Text
unqualifiedJobModuleName :: JobConfig -> Text
unqualifiedJobModuleName JobConfig
config = Text -> Text -> Text -> Text
Text.replace Text
"Job" Text
"" (Proxy "modelName" -> JobConfig -> Text
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "modelName" (Proxy "modelName")
Proxy "modelName"
#modelName JobConfig
config)

buildPlan' :: JobConfig -> [GeneratorAction]
buildPlan' :: JobConfig -> [GeneratorAction]
buildPlan' JobConfig
config =
        let
            name :: Text
name = Proxy "modelName" -> JobConfig -> Text
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "modelName" (Proxy "modelName")
Proxy "modelName"
#modelName JobConfig
config
            tableName :: Text
tableName = Text -> Text
modelNameToTableName Text
nameWithSuffix
            nameWithSuffix :: Text
nameWithSuffix = if Text
"Job" Text -> Text -> Bool
`isSuffixOf` Text
name
                then Text
name
                else Text
name Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"Job" --e.g. "Test" -> "TestJob"
            nameWithoutSuffix :: Text
nameWithoutSuffix = if Text
"Job" Text -> Text -> Bool
`isSuffixOf` Text
name
                then Text -> Text -> Text -> Text
Text.replace Text
"Job" Text
"" Text
name
                else Text
name --e.g. "TestJob" -> "Test""

            job :: Text
job =
                Text
""
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"module " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> JobConfig -> Text
qualifiedJobModuleName JobConfig
config Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" where\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"import " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Proxy "applicationName" -> JobConfig -> Text
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "applicationName" (Proxy "applicationName")
Proxy "applicationName"
#applicationName JobConfig
config Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
".Controller.Prelude\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"instance Job " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
nameWithSuffix Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" where\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    perform " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
nameWithSuffix Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" { .. } = do\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"        putStrLn \"Hello World!\"\n"

            schemaSql :: Text
schemaSql =
                Text
""
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"CREATE TABLE " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
tableName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" (\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL,\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    status JOB_STATUS DEFAULT 'job_status_not_started' NOT NULL,\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    last_error TEXT DEFAULT NULL,\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    attempts_count INT DEFAULT 0 NOT NULL,\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    locked_at TIMESTAMP WITH TIME ZONE DEFAULT NULL,\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    locked_by UUID DEFAULT NULL,\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"    run_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL\n"
                Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
");\n"


            emptyWorkerHs :: Text
            emptyWorkerHs :: Text
emptyWorkerHs =
                        let
                            applicationName :: Text
applicationName = Proxy "applicationName" -> JobConfig -> Text
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "applicationName" (Proxy "applicationName")
Proxy "applicationName"
#applicationName JobConfig
config
                        in String -> Text
forall a b. ConvertibleStrings a b => a -> b
cs [plain|module #{applicationName}.Worker where

import IHP.Prelude
import #{applicationName}.Types
import Generated.Types
import IHP.Job.Runner
import IHP.Job.Types

import #{qualifiedJobModuleName config}

instance Worker #{applicationName}Application where
    workers _ =
        [ worker @#{nameWithSuffix}
        -- Generator Marker
        ]
|]
        in
            [ EnsureDirectory :: Text -> GeneratorAction
EnsureDirectory { $sel:directory:CreateFile :: Text
directory = Proxy "applicationName" -> JobConfig -> Text
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "applicationName" (Proxy "applicationName")
Proxy "applicationName"
#applicationName JobConfig
config Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/Job" }
            , AppendToFile :: Text -> Text -> GeneratorAction
AppendToFile { $sel:filePath:CreateFile :: Text
filePath = Text
"Application/Schema.sql", $sel:fileContent:CreateFile :: Text
fileContent = Text
schemaSql }
            , CreateFile :: Text -> Text -> GeneratorAction
CreateFile { $sel:filePath:CreateFile :: Text
filePath = Proxy "applicationName" -> JobConfig -> Text
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "applicationName" (Proxy "applicationName")
Proxy "applicationName"
#applicationName JobConfig
config Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/Job/" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
nameWithoutSuffix Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
".hs", $sel:fileContent:CreateFile :: Text
fileContent = Text
job }
            ]
            [GeneratorAction] -> [GeneratorAction] -> [GeneratorAction]
forall a. Semigroup a => a -> a -> a
<> if Proxy "isFirstJobInApplication" -> JobConfig -> Bool
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "isFirstJobInApplication" (Proxy "isFirstJobInApplication")
Proxy "isFirstJobInApplication"
#isFirstJobInApplication JobConfig
config
                    then [ CreateFile :: Text -> Text -> GeneratorAction
CreateFile { $sel:filePath:CreateFile :: Text
filePath = Proxy "applicationName" -> JobConfig -> Text
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "applicationName" (Proxy "applicationName")
Proxy "applicationName"
#applicationName JobConfig
config Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/Worker.hs", $sel:fileContent:CreateFile :: Text
fileContent = Text
emptyWorkerHs } ]
                    else
                        [ AddImport :: Text -> Text -> GeneratorAction
AddImport { $sel:filePath:CreateFile :: Text
filePath = Proxy "applicationName" -> JobConfig -> Text
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "applicationName" (Proxy "applicationName")
Proxy "applicationName"
#applicationName JobConfig
config Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/Worker.hs", $sel:fileContent:CreateFile :: Text
fileContent = Text
"import " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> JobConfig -> Text
qualifiedJobModuleName JobConfig
config }
                        , AppendToMarker :: Text -> Text -> Text -> GeneratorAction
AppendToMarker { $sel:marker:CreateFile :: Text
marker = Text
"-- Generator Marker", $sel:filePath:CreateFile :: Text
filePath = Proxy "applicationName" -> JobConfig -> Text
forall model (name :: Symbol) value.
(KnownSymbol name, HasField name model value) =>
Proxy name -> model -> value
get IsLabel "applicationName" (Proxy "applicationName")
Proxy "applicationName"
#applicationName JobConfig
config Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"/Worker.hs", $sel:fileContent:CreateFile :: Text
fileContent = Text
"        , worker @" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
nameWithSuffix }
                        ]