hunk.code_window: {
     "name": "firebase2graphql",
     "description": "A CLI tool to get GraphQL over Firebase data dump",
     "version": "0.0.1-alpha7",
     "author": "Hasura",
     "bin": {
       "firebase2graphql": "./bin/run",
       "f2g": "./bin/run"
  
hunk.labels: keep keep keep replace keep keep keep keep
hunk.after_edit:   "version": "0.0.1-alpha8",
  
hunk.file_path: community/tools/firebase2graphql/package.json
hunk.type: replace
file: {-# LANGUAGE DeriveLift                 #-}
  {-# LANGUAGE FlexibleContexts           #-}
  {-# LANGUAGE FlexibleInstances          #-}
  {-# LANGUAGE GeneralizedNewtypeDeriving #-}
  {-# LANGUAGE OverloadedStrings          #-}
  {-# LANGUAGE QuasiQuotes                #-}
  {-# LANGUAGE TemplateHaskell            #-}
  {-# LANGUAGE TypeFamilies               #-}
  
  module Hasura.RQL.DDL.QueryTemplate
    ( createQueryTemplateP1
    , createQueryTemplateP2
    , delQTemplateFromCatalog
    , TemplateParamConf(..)
    , CreateQueryTemplate(..)
    , DropQueryTemplate(..)
    , QueryTP1
    , SetQueryTemplateComment(..)
    ) where
  
  import           Hasura.Prelude
  import           Hasura.RQL.GBoolExp        (txtRHSBuilder)
  import           Hasura.RQL.Types
  import           Hasura.SQL.Types
  import           Hasura.SQL.Value
  
  import qualified Database.PG.Query          as Q
  import qualified Hasura.RQL.DML.Count       as R
  import qualified Hasura.RQL.DML.Delete      as R
  import qualified Hasura.RQL.DML.Insert      as R
  import qualified Hasura.RQL.DML.Select      as R
  import qualified Hasura.RQL.DML.Update      as R
  import qualified Hasura.SQL.DML             as PS
  
  import           Data.Aeson
  import           Data.Aeson.Casing
  import           Data.Aeson.TH
  import           Instances.TH.Lift          ()
  import           Language.Haskell.TH.Syntax (Lift)
  
  import qualified Data.HashMap.Strict        as M
  import qualified Data.Text                  as T
  
  data TemplateParamConf
    = TemplateParamConf
    { tpcParam   :: !TemplateParam
    , tpcDefault :: !(Maybe Value)
    } deriving (Show, Eq, Lift)
  
  $(deriveJSON (aesonDrop 3 snakeCase){omitNothingFields=True} ''TemplateParamConf)
  
  data CreateQueryTemplate
    = CreateQueryTemplate
    { cqtName     :: !TQueryName
    , cqtTemplate :: !QueryT
    , cqtComment  :: !(Maybe T.Text)
    } deriving (Show, Eq, Lift)
  
  $(deriveJSON (aesonDrop 3 snakeCase){omitNothingFields=True} ''CreateQueryTemplate)
  
  validateParam
    :: PGColType
    -> Value
    -> P1 PS.SQLExp
  validateParam pct val =
    case val of
      Object _ -> do
        tpc <- decodeValue val
        withPathK "default" $
          maybe (return ()) validateDefault $ tpcDefault tpc
        return $ PS.SELit "NULL"
      _ -> txtRHSBuilder pct val
    where
      validateDefault =
        void . runAesonParser (convToBin pct)
  
  mkSelQ :: SelectQueryT -> P1 SelectQuery
  mkSelQ (DMLQuery tn (SelectG c w o lim offset)) = do
    intLim <- withPathK "limit" $ maybe returnNothing parseAsInt lim
    intOffset <- withPathK "offset" $ maybe returnNothing parseAsInt offset
    return $ DMLQuery tn $ SelectG c w o intLim intOffset
    where
      returnNothing = return Nothing
      parseAsInt v = case v of
        Object _ -> do
          tpc <- decodeValue v
          withPathK "default" $
            mapM decodeValue $ tpcDefault tpc
        _ -> Just <$> decodeValue v
  
  data QueryTP1
    = QTP1Insert R.InsertQueryP1
    | QTP1Select R.AnnSel
    | QTP1Update R.UpdateQueryP1
    | QTP1Delete R.DeleteQueryP1
    | QTP1Count R.CountQueryP1
    | QTP1Bulk [QueryTP1]
    deriving (Show, Eq)
  
  validateTQuery
    :: QueryT
    -> P1 QueryTP1
  validateTQuery qt = withPathK "args" $ case qt of
    QTInsert q -> QTP1Insert <$> R.convInsertQuery decodeInsObjs validateParam q
    QTSelect q -> QTP1Select <$> (mkSelQ q >>= R.convSelectQuery validateParam)
    QTUpdate q -> QTP1Update <$> R.convUpdateQuery validateParam q
    QTDelete q -> QTP1Delete <$> R.convDeleteQuery validateParam q
    QTCount q  -> QTP1Count <$> R.countP1 validateParam q
    QTBulk q   -> QTP1Bulk <$> mapM validateTQuery q
    where
      decodeInsObjs val = do
        tpc <- decodeValue val
        mDefObjs <- mapM decodeValue $ tpcDefault tpc
        return $ fromMaybe [] mDefObjs
  
  collectDeps
    :: QueryTP1 -> [SchemaDependency]
  collectDeps qt = case qt of
    QTP1Insert qp1 -> R.getInsertDeps qp1
    QTP1Select qp1 -> R.getSelectDeps qp1
    QTP1Update qp1 -> R.getUpdateDeps qp1
    QTP1Delete qp1 -> R.getDeleteDeps qp1
    QTP1Count qp1  -> R.getCountDeps qp1
    QTP1Bulk qp1   -> concatMap collectDeps qp1
  
  createQueryTemplateP1
    :: (P1C m) => CreateQueryTemplate -> m QueryTemplateInfo
  createQueryTemplateP1 (CreateQueryTemplate qtn qt _) = do
    adminOnly
    ui <- askUserInfo
    sc <- askSchemaCache
    withPathK "name" $ when (isJust $ M.lookup qtn $ scQTemplates sc) $
      throw400 AlreadyExists $ "the query template already exists : " <>> qtn
    let qCtx = QCtx ui sc
    qtp1 <- withPathK "template" $ liftP1 qCtx $ validateTQuery qt
    let deps = collectDeps qtp1
    return $ QueryTemplateInfo qtn qt deps
  
  addQTemplateToCatalog
    :: CreateQueryTemplate
    -> Q.TxE QErr ()
  addQTemplateToCatalog (CreateQueryTemplate qtName qtDef mComment) =
    Q.unitQE defaultTxErrorHandler [Q.sql|
             INSERT INTO
                    hdb_catalog.hdb_query_template
                    (template_name, template_defn, comment)
             VALUES ($1, $2 :: jsonb, $3)
                  |] (qtName, Q.AltJ qtDef, mComment) False
  
  createQueryTemplateP2
    :: (P2C m)
    => CreateQueryTemplate -> QueryTemplateInfo -> m RespBody
  createQueryTemplateP2 cqt qti = do
    addQTemplateToCache qti
    liftTx $ addQTemplateToCatalog cqt
    return successMsg
  
  instance HDBQuery CreateQueryTemplate where
  
    type Phase1Res CreateQueryTemplate = QueryTemplateInfo
    phaseOne = createQueryTemplateP1
  
    phaseTwo = createQueryTemplateP2
  
    schemaCachePolicy = SCPReload
  
  data DropQueryTemplate
    = DropQueryTemplate
    { dqtName :: !TQueryName
    } deriving (Show, Eq, Lift)
  
  $(deriveJSON (aesonDrop 3 snakeCase){omitNothingFields=True} ''DropQueryTemplate)
  
  delQTemplateFromCatalog
    :: TQueryName
    -> Q.TxE QErr ()
  delQTemplateFromCatalog qtn =
    Q.unitQE defaultTxErrorHandler [Q.sql|
             DELETE FROM
                    hdb_catalog.hdb_query_template
             WHERE template_name =  $1
                  |] (Identity qtn) False
  
  instance HDBQuery DropQueryTemplate where
  
    type Phase1Res DropQueryTemplate = ()
    phaseOne (DropQueryTemplate qtn) =
      withPathK "name" $ void $ askQTemplateInfo qtn
  
    phaseTwo (DropQueryTemplate qtn) _ = do
      delQTemplateFromCache qtn
      liftTx $ delQTemplateFromCatalog qtn
      return successMsg
  
    schemaCachePolicy = SCPReload
  
  data SetQueryTemplateComment
    = SetQueryTemplateComment
    { sqtcName    :: !TQueryName
    , sqtcComment :: !(Maybe T.Text)
    } deriving (Show, Eq, Lift)
  
  $(deriveJSON (aesonDrop 4 snakeCase) ''SetQueryTemplateComment)
  
  setQueryTemplateCommentP1 :: (P1C m) => SetQueryTemplateComment -> m ()
  setQueryTemplateCommentP1 (SetQueryTemplateComment qtn _) = do
    adminOnly
    void $ askQTemplateInfo qtn
  
  setQueryTemplateCommentP2 :: (P2C m) => SetQueryTemplateComment -> m RespBody
  setQueryTemplateCommentP2 apc = do
    liftTx $ setQueryTemplateCommentTx apc
    return successMsg
  
  instance HDBQuery SetQueryTemplateComment where
  
    type Phase1Res SetQueryTemplateComment = ()
    phaseOne = setQueryTemplateCommentP1
  
    phaseTwo q _ = setQueryTemplateCommentP2 q
  
    schemaCachePolicy = SCPNoChange
  
  setQueryTemplateCommentTx
    :: SetQueryTemplateComment
    -> Q.TxE QErr ()
  setQueryTemplateCommentTx (SetQueryTemplateComment qtn comment) =
    Q.unitQE defaultTxErrorHandler
    [Q.sql|
      UPDATE hdb_catalog.hdb_query_template
      SET comment = $1
      WHERE template_name =  $2
          |] (comment, qtn) False
  