Refactor: remove pjKeys from RawJSON
This commit is contained in:
committed by
Steve Chávez
parent
553531711b
commit
033ee5a06e
+11
-10
@@ -19,7 +19,6 @@ import qualified Data.ByteString as BS
|
|||||||
import qualified Data.ByteString.Internal as BS (c2w)
|
import qualified Data.ByteString.Internal as BS (c2w)
|
||||||
import qualified Data.ByteString.Lazy as BL
|
import qualified Data.ByteString.Lazy as BL
|
||||||
import qualified Data.Csv as CSV
|
import qualified Data.Csv as CSV
|
||||||
import Data.Either.Combinators (mapLeft)
|
|
||||||
import qualified Data.List as L
|
import qualified Data.List as L
|
||||||
import Data.List (lookup, last, partition)
|
import Data.List (lookup, last, partition)
|
||||||
import qualified Data.HashMap.Strict as M
|
import qualified Data.HashMap.Strict as M
|
||||||
@@ -33,7 +32,6 @@ import Network.HTTP.Types.Header (hAuthorization, hCookie)
|
|||||||
import Network.HTTP.Types.URI (parseSimpleQuery)
|
import Network.HTTP.Types.URI (parseSimpleQuery)
|
||||||
import Network.Wai (Request (..))
|
import Network.Wai (Request (..))
|
||||||
import Network.Wai.Parse (parseHttpAccept)
|
import Network.Wai.Parse (parseHttpAccept)
|
||||||
import PostgREST.Parsers (pRequestColumns)
|
|
||||||
import PostgREST.RangeQuery (NonnegRange, rangeRequested, restrictRange, rangeGeq, allRange, rangeLimit, rangeOffset)
|
import PostgREST.RangeQuery (NonnegRange, rangeRequested, restrictRange, rangeGeq, allRange, rangeLimit, rangeOffset)
|
||||||
import Data.Ranged.Boundaries
|
import Data.Ranged.Boundaries
|
||||||
import PostgREST.Types
|
import PostgREST.Types
|
||||||
@@ -90,6 +88,8 @@ data ApiRequest = ApiRequest {
|
|||||||
, iLogic :: [(Text, Text)]
|
, iLogic :: [(Text, Text)]
|
||||||
-- | &select parameter used to shape the response
|
-- | &select parameter used to shape the response
|
||||||
, iSelect :: Text
|
, iSelect :: Text
|
||||||
|
-- | &columns parameter used to shape the payload
|
||||||
|
, iColumns :: Maybe Text
|
||||||
-- | &order parameters for each level
|
-- | &order parameters for each level
|
||||||
, iOrder :: [(Text, Text)]
|
, iOrder :: [(Text, Text)]
|
||||||
-- | Alphabetized (canonical) request query string for response URLs
|
-- | Alphabetized (canonical) request query string for response URLs
|
||||||
@@ -123,6 +123,7 @@ userApiRequest schema req reqBody
|
|||||||
, iFilters = filters
|
, iFilters = filters
|
||||||
, iLogic = [(toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, endingIn ["and", "or"] k ]
|
, iLogic = [(toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, endingIn ["and", "or"] k ]
|
||||||
, iSelect = toS $ fromMaybe "*" $ join $ lookup "select" qParams
|
, iSelect = toS $ fromMaybe "*" $ join $ lookup "select" qParams
|
||||||
|
, iColumns = columns
|
||||||
, iOrder = [(toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, endingIn ["order"] k ]
|
, iOrder = [(toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, endingIn ["order"] k ]
|
||||||
, iCanonicalQS = toS $ urlEncodeVars
|
, iCanonicalQS = toS $ urlEncodeVars
|
||||||
. L.sortBy (comparing fst)
|
. L.sortBy (comparing fst)
|
||||||
@@ -150,19 +151,19 @@ userApiRequest schema req reqBody
|
|||||||
isEmbedPath = T.isInfixOf "."
|
isEmbedPath = T.isInfixOf "."
|
||||||
isTargetingProc = (== Just "rpc") $ listToMaybe path
|
isTargetingProc = (== Just "rpc") $ listToMaybe path
|
||||||
contentType = decodeContentType . fromMaybe "application/json" $ lookupHeader "content-type"
|
contentType = decodeContentType . fromMaybe "application/json" $ lookupHeader "content-type"
|
||||||
|
columns | action `elem` [ActionCreate, ActionUpdate, ActionInvoke{isReadOnly=False}] = toS <$> join (lookup "columns" qParams)
|
||||||
|
| otherwise = Nothing
|
||||||
payload =
|
payload =
|
||||||
case (contentType, action) of
|
case (contentType, action) of
|
||||||
(_, ActionInvoke{isReadOnly=True}) ->
|
(_, ActionInvoke{isReadOnly=True}) ->
|
||||||
Right $ ProcessedJSON (JSON.encode $ M.fromList $ second JSON.toJSON <$> rpcQParams) PJObject (S.fromList $ fst <$> rpcQParams)
|
Right $ ProcessedJSON (JSON.encode $ M.fromList $ second JSON.toJSON <$> rpcQParams) PJObject (S.fromList $ fst <$> rpcQParams)
|
||||||
(CTApplicationJSON, _) ->
|
(CTApplicationJSON, _) ->
|
||||||
let columns | action `elem` [ActionCreate, ActionUpdate, ActionInvoke{isReadOnly=False}] = toS <$> join (lookup "columns" qParams)
|
if isJust columns
|
||||||
| otherwise = Nothing in
|
then Right $ RawJSON reqBody
|
||||||
case columns of
|
else note "All object keys must match" . payloadAttributes reqBody
|
||||||
Just cols -> RawJSON reqBody <$> mapLeft show (pRequestColumns cols)
|
=<< if BL.null reqBody && isTargetingProc
|
||||||
Nothing -> note "All object keys must match" . payloadAttributes reqBody
|
then Right emptyObject
|
||||||
=<< if BL.null reqBody && isTargetingProc
|
else JSON.eitherDecode reqBody
|
||||||
then Right emptyObject
|
|
||||||
else JSON.eitherDecode reqBody
|
|
||||||
(CTTextCSV, _) -> do
|
(CTTextCSV, _) -> do
|
||||||
json <- csvToJson <$> CSV.decodeByName reqBody
|
json <- csvToJson <$> CSV.decodeByName reqBody
|
||||||
note "All lines must have same number of fields" $ payloadAttributes (JSON.encode json) json
|
note "All lines must have same number of fields" $ payloadAttributes (JSON.encode json) json
|
||||||
|
|||||||
+26
-29
@@ -56,6 +56,7 @@ import PostgREST.QueryBuilder ( callProc
|
|||||||
, createWriteStatement
|
, createWriteStatement
|
||||||
, ResultsWithCount
|
, ResultsWithCount
|
||||||
)
|
)
|
||||||
|
import PostgREST.Parsers (pRequestColumns)
|
||||||
import PostgREST.Types
|
import PostgREST.Types
|
||||||
import PostgREST.OpenAPI
|
import PostgREST.OpenAPI
|
||||||
|
|
||||||
@@ -74,33 +75,29 @@ postgrest conf refDbStructure pool getTime worker =
|
|||||||
case maybeDbStructure of
|
case maybeDbStructure of
|
||||||
Nothing -> respond connectionLostError
|
Nothing -> respond connectionLostError
|
||||||
Just dbStructure -> do
|
Just dbStructure -> do
|
||||||
response <- case userApiRequest (configSchema conf) req body of
|
response <- do
|
||||||
Left err -> return $ apiRequestError err
|
-- Need to parse ?columns early because findProc needs it to solve overloaded functions
|
||||||
Right apiRequest -> do
|
let apiReq = userApiRequest (configSchema conf) req body
|
||||||
eClaims <- jwtClaims jwtSecret (configJwtAudience conf) (toS $ iJWT apiRequest) time (rightToMaybe $ configRoleClaimKey conf)
|
apiReqCols = (,) <$> apiReq <*> (pRequestColumns =<< iColumns <$> apiReq)
|
||||||
let authed = containsRole eClaims
|
case apiReqCols of
|
||||||
proc = case (iTarget apiRequest, iPayload apiRequest, iPreferSingleObjectParameter apiRequest) of
|
Left err -> return $ apiRequestError err
|
||||||
(TargetProc qi, Just pJson, s) -> findProc qi (pjKeys pJson) s $ dbProcs dbStructure
|
Right (apiRequest, maybeCols) -> do
|
||||||
_ -> Nothing
|
eClaims <- jwtClaims jwtSecret (configJwtAudience conf) (toS $ iJWT apiRequest) time (rightToMaybe $ configRoleClaimKey conf)
|
||||||
handleReq = runWithClaims conf eClaims (app dbStructure proc conf) apiRequest
|
let authed = containsRole eClaims
|
||||||
txMode = transactionMode proc (iAction apiRequest)
|
cols = case (iPayload apiRequest, maybeCols) of
|
||||||
response <- P.use pool $ HT.transaction HT.ReadCommitted txMode handleReq
|
(Just ProcessedJSON{pjKeys}, _) -> pjKeys
|
||||||
return $ either (pgError authed) identity response
|
(Just RawJSON{}, Just cls) -> cls
|
||||||
|
_ -> S.empty
|
||||||
|
proc = case iTarget apiRequest of
|
||||||
|
TargetProc qi -> findProc qi cols (iPreferSingleObjectParameter apiRequest) $ dbProcs dbStructure
|
||||||
|
_ -> Nothing
|
||||||
|
handleReq = runWithClaims conf eClaims (app dbStructure proc cols conf) apiRequest
|
||||||
|
txMode = transactionMode proc (iAction apiRequest)
|
||||||
|
response <- P.use pool $ HT.transaction HT.ReadCommitted txMode handleReq
|
||||||
|
return $ either (pgError authed) identity response
|
||||||
when (responseStatus response == status503) worker
|
when (responseStatus response == status503) worker
|
||||||
respond response
|
respond response
|
||||||
|
|
||||||
findProc :: QualifiedIdentifier -> S.Set Text -> Bool -> M.HashMap Text [ProcDescription] -> Maybe ProcDescription
|
|
||||||
findProc qi payloadKeys paramsAsSingleObject allProcs =
|
|
||||||
let procs = M.lookup (qiName qi) allProcs in
|
|
||||||
-- Handle overloaded functions case
|
|
||||||
join $ (case length <$> procs of
|
|
||||||
Just 1 -> headMay -- if it's not an overloaded function then immediatly get the ProcDescription
|
|
||||||
_ -> find (\x ->
|
|
||||||
if paramsAsSingleObject
|
|
||||||
then length (pdArgs x) == 1 -- if the arg is not of json type let the db give the err
|
|
||||||
else payloadKeys `S.isSubsetOf` S.fromList (pgaName <$> pdArgs x))
|
|
||||||
) <$> procs
|
|
||||||
|
|
||||||
transactionMode :: Maybe ProcDescription -> Action -> HT.Mode
|
transactionMode :: Maybe ProcDescription -> Action -> HT.Mode
|
||||||
transactionMode proc action =
|
transactionMode proc action =
|
||||||
case action of
|
case action of
|
||||||
@@ -115,8 +112,8 @@ transactionMode proc action =
|
|||||||
ActionInvoke{isReadOnly=True} -> HT.Read
|
ActionInvoke{isReadOnly=True} -> HT.Read
|
||||||
_ -> HT.Write
|
_ -> HT.Write
|
||||||
|
|
||||||
app :: DbStructure -> Maybe ProcDescription -> AppConfig -> ApiRequest -> H.Transaction Response
|
app :: DbStructure -> Maybe ProcDescription -> S.Set FieldName -> AppConfig -> ApiRequest -> H.Transaction Response
|
||||||
app dbStructure proc conf apiRequest =
|
app dbStructure proc cols conf apiRequest =
|
||||||
case responseContentTypeOrError (iAccepts apiRequest) (iAction apiRequest) of
|
case responseContentTypeOrError (iAccepts apiRequest) (iAction apiRequest) of
|
||||||
Left errorResponse -> return errorResponse
|
Left errorResponse -> return errorResponse
|
||||||
Right contentType ->
|
Right contentType ->
|
||||||
@@ -189,7 +186,7 @@ app dbStructure proc conf apiRequest =
|
|||||||
row <- H.statement (toS $ pjRaw pJson) stm
|
row <- H.statement (toS $ pjRaw pJson) stm
|
||||||
let (_, queryTotal, _, body) = extractQueryResult row
|
let (_, queryTotal, _, body) = extractQueryResult row
|
||||||
|
|
||||||
updateIsNoOp = S.null $ pjKeys pJson
|
updateIsNoOp = S.null cols
|
||||||
contentRangeHeader = contentRangeH 0 (queryTotal - 1) $
|
contentRangeHeader = contentRangeH 0 (queryTotal - 1) $
|
||||||
if shouldCount then Just queryTotal else Nothing
|
if shouldCount then Just queryTotal else Nothing
|
||||||
minimalHeaders = [contentRangeHeader]
|
minimalHeaders = [contentRangeHeader]
|
||||||
@@ -285,7 +282,7 @@ app dbStructure proc conf apiRequest =
|
|||||||
Left errorResponse -> return errorResponse
|
Left errorResponse -> return errorResponse
|
||||||
Right ((q, cq), bField) -> do
|
Right ((q, cq), bField) -> do
|
||||||
let singular = contentType == CTSingularJSON
|
let singular = contentType == CTSingularJSON
|
||||||
specifiedPgArgs = filter ((`S.member` pjKeys pJson) . pgaName) $ maybe [] pdArgs proc
|
specifiedPgArgs = filter ((`S.member` cols) . pgaName) $ maybe [] pdArgs proc
|
||||||
row <- H.statement (toS $ pjRaw pJson) $
|
row <- H.statement (toS $ pjRaw pJson) $
|
||||||
callProc qi specifiedPgArgs returnsScalar q cq shouldCount
|
callProc qi specifiedPgArgs returnsScalar q cq shouldCount
|
||||||
singular (iPreferSingleObjectParameter apiRequest)
|
singular (iPreferSingleObjectParameter apiRequest)
|
||||||
@@ -339,7 +336,7 @@ app dbStructure proc conf apiRequest =
|
|||||||
selectQuery = requestToQuery schema False <$> readDbRequest
|
selectQuery = requestToQuery schema False <$> readDbRequest
|
||||||
countQuery = requestToCountQuery schema <$> readDbRequest
|
countQuery = requestToCountQuery schema <$> readDbRequest
|
||||||
readSqlParts = (,) <$> selectQuery <*> countQuery
|
readSqlParts = (,) <$> selectQuery <*> countQuery
|
||||||
mutationDbRequest s t = mutateRequest apiRequest t (tablePKCols dbStructure s t) =<< fldNames
|
mutationDbRequest s t = mutateRequest apiRequest t cols (tablePKCols dbStructure s t) =<< fldNames
|
||||||
mutateSqlParts s t =
|
mutateSqlParts s t =
|
||||||
(,) <$> selectQuery
|
(,) <$> selectQuery
|
||||||
<*> (requestToQuery schema False . DbMutate <$> mutationDbRequest s t)
|
<*> (requestToQuery schema False . DbMutate <$> mutationDbRequest s t)
|
||||||
|
|||||||
@@ -318,11 +318,11 @@ addProperty f (targetNodeName:remainingPath, a) (Node rn forest) =
|
|||||||
where
|
where
|
||||||
pathNode = find (\(Node (_,(nodeName,_,alias,_,_)) _) -> nodeName == targetNodeName || alias == Just targetNodeName) forest
|
pathNode = find (\(Node (_,(nodeName,_,alias,_,_)) _) -> nodeName == targetNodeName || alias == Just targetNodeName) forest
|
||||||
|
|
||||||
mutateRequest :: ApiRequest -> TableName -> [Text] -> [FieldName] -> Either Response MutateRequest
|
mutateRequest :: ApiRequest -> TableName -> S.Set FieldName -> [FieldName] -> [FieldName] -> Either Response MutateRequest
|
||||||
mutateRequest apiRequest tName pkCols fldNames = mapLeft apiRequestError $
|
mutateRequest apiRequest tName cols pkCols fldNames = mapLeft apiRequestError $
|
||||||
case action of
|
case action of
|
||||||
ActionCreate -> Right $ Insert tName pjCols ((,) <$> iPreferResolution apiRequest <*> Just pkCols) [] returnings
|
ActionCreate -> Right $ Insert tName cols ((,) <$> iPreferResolution apiRequest <*> Just pkCols) [] returnings
|
||||||
ActionUpdate -> Update tName pjCols <$> combinedLogic <*> pure returnings
|
ActionUpdate -> Update tName cols <$> combinedLogic <*> pure returnings
|
||||||
ActionSingleUpsert ->
|
ActionSingleUpsert ->
|
||||||
(\flts ->
|
(\flts ->
|
||||||
if null (iLogic apiRequest) &&
|
if null (iLogic apiRequest) &&
|
||||||
@@ -331,14 +331,13 @@ mutateRequest apiRequest tName pkCols fldNames = mapLeft apiRequestError $
|
|||||||
all (\case
|
all (\case
|
||||||
Filter _ (OpExpr False (Op "eq" _)) -> True
|
Filter _ (OpExpr False (Op "eq" _)) -> True
|
||||||
_ -> False) flts
|
_ -> False) flts
|
||||||
then Insert tName pjCols (Just (MergeDuplicates, pkCols)) <$> combinedLogic <*> pure returnings
|
then Insert tName cols (Just (MergeDuplicates, pkCols)) <$> combinedLogic <*> pure returnings
|
||||||
else
|
else
|
||||||
Left InvalidFilters) =<< filters
|
Left InvalidFilters) =<< filters
|
||||||
ActionDelete -> Delete tName <$> combinedLogic <*> pure returnings
|
ActionDelete -> Delete tName <$> combinedLogic <*> pure returnings
|
||||||
_ -> Left UnsupportedVerb
|
_ -> Left UnsupportedVerb
|
||||||
where
|
where
|
||||||
action = iAction apiRequest
|
action = iAction apiRequest
|
||||||
pjCols = pjKeys $ fromJust $ iPayload apiRequest
|
|
||||||
returnings = if iPreferRepresentation apiRequest == None then [] else fldNames
|
returnings = if iPreferRepresentation apiRequest == None then [] else fldNames
|
||||||
filters = map snd <$> mapM pRequestFilter mutateFilters
|
filters = map snd <$> mapM pRequestFilter mutateFilters
|
||||||
logic = map snd <$> mapM pRequestLogicTree logicFilters
|
logic = map snd <$> mapM pRequestLogicTree logicFilters
|
||||||
|
|||||||
@@ -56,6 +56,13 @@ pRequestLogicTree (k, v) = mapError $ (,) <$> embedPath <*> logicTree
|
|||||||
-- Concat op and v to make pLogicTree argument regular, in the form of "?and=and(.. , ..)" instead of "?and=(.. , ..)"
|
-- Concat op and v to make pLogicTree argument regular, in the form of "?and=and(.. , ..)" instead of "?and=(.. , ..)"
|
||||||
logicTree = join $ parse pLogicTree ("failed to parse logic tree (" ++ toS v ++ ")") . toS <$> ((<>) <$> op <*> pure v)
|
logicTree = join $ parse pLogicTree ("failed to parse logic tree (" ++ toS v ++ ")") . toS <$> ((<>) <$> op <*> pure v)
|
||||||
|
|
||||||
|
pRequestColumns :: Maybe Text -> Either ApiRequestError (Maybe (S.Set FieldName))
|
||||||
|
pRequestColumns colStr =
|
||||||
|
case colStr of
|
||||||
|
Just str ->
|
||||||
|
mapError $ Just . S.fromList <$> parse pColumns ("failed to parse columns parameter (" <> toS str <> ")") (toS str)
|
||||||
|
_ -> Right Nothing
|
||||||
|
|
||||||
ws :: Parser Text
|
ws :: Parser Text
|
||||||
ws = toS <$> many (oneOf " \t")
|
ws = toS <$> many (oneOf " \t")
|
||||||
|
|
||||||
@@ -218,10 +225,6 @@ pLogicPath = do
|
|||||||
notOp = "not." <> op
|
notOp = "not." <> op
|
||||||
return (filter (/= "not") (init path), if "not" `elem` path then notOp else op)
|
return (filter (/= "not") (init path), if "not" `elem` path then notOp else op)
|
||||||
|
|
||||||
pRequestColumns :: Text -> Either ParseError (S.Set FieldName)
|
|
||||||
pRequestColumns colStr =
|
|
||||||
S.fromList <$> parse pColumns ("failed to parse columns parameter (" <> toS colStr <> ")") (toS colStr)
|
|
||||||
|
|
||||||
pColumns :: Parser [FieldName]
|
pColumns :: Parser [FieldName]
|
||||||
pColumns = pFieldName `sepBy1` lexeme (char ',')
|
pColumns = pFieldName `sepBy1` lexeme (char ',')
|
||||||
|
|
||||||
|
|||||||
+16
-1
@@ -95,6 +95,22 @@ instance Ord ProcDescription where
|
|||||||
| name1 == name2 && length args1 > length args2 = GT
|
| name1 == name2 && length args1 > length args2 = GT
|
||||||
| otherwise = (name1, des1, args1, rt1, vol1) `compare` (name2, des2, args2, rt2, vol2)
|
| otherwise = (name1, des1, args1, rt1, vol1) `compare` (name2, des2, args2, rt2, vol2)
|
||||||
|
|
||||||
|
{-|
|
||||||
|
Search a pg procedure by its parameters, since a function can be overloaded the name is not enough to find it.
|
||||||
|
An overloaded function can have a different volatility or even a different return type.
|
||||||
|
-}
|
||||||
|
findProc :: QualifiedIdentifier -> S.Set Text -> Bool -> M.HashMap Text [ProcDescription] -> Maybe ProcDescription
|
||||||
|
findProc qi payloadKeys paramsAsSingleObject allProcs =
|
||||||
|
let procs = M.lookup (qiName qi) allProcs in
|
||||||
|
-- Handle overloaded functions case
|
||||||
|
join $ (case length <$> procs of
|
||||||
|
Just 1 -> headMay -- if it's not an overloaded function then immediatly get the ProcDescription
|
||||||
|
_ -> find (\x ->
|
||||||
|
if paramsAsSingleObject
|
||||||
|
then length (pdArgs x) == 1 -- if the arg is not of json type let the db give the err
|
||||||
|
else payloadKeys `S.isSubsetOf` S.fromList (pgaName <$> pdArgs x))
|
||||||
|
) <$> procs
|
||||||
|
|
||||||
type Schema = Text
|
type Schema = Text
|
||||||
type TableName = Text
|
type TableName = Text
|
||||||
type SqlQuery = Text
|
type SqlQuery = Text
|
||||||
@@ -200,7 +216,6 @@ data PayloadJSON =
|
|||||||
}|
|
}|
|
||||||
RawJSON {
|
RawJSON {
|
||||||
pjRaw :: BL.ByteString
|
pjRaw :: BL.ByteString
|
||||||
, pjKeys :: S.Set Text
|
|
||||||
} deriving (Show, Eq)
|
} deriving (Show, Eq)
|
||||||
|
|
||||||
data PJType = PJArray { pjaLength :: Int } | PJObject deriving (Show, Eq)
|
data PJType = PJArray { pjaLength :: Int } | PJObject deriving (Show, Eq)
|
||||||
|
|||||||
@@ -533,7 +533,7 @@ spec actualPgVersion = do
|
|||||||
simpleBody p `shouldBe` "["<>payload<>"]"
|
simpleBody p `shouldBe` "["<>payload<>"]"
|
||||||
simpleStatus p `shouldBe` ok200
|
simpleStatus p `shouldBe` ok200
|
||||||
|
|
||||||
context "PATCH with ?columns parameter" $
|
context "PATCH with ?columns parameter" $ do
|
||||||
it "ignores json keys not included in ?columns" $
|
it "ignores json keys not included in ?columns" $
|
||||||
request methodPatch "/articles?id=eq.200&columns=body" [("Prefer", "return=representation")]
|
request methodPatch "/articles?id=eq.200&columns=body" [("Prefer", "return=representation")]
|
||||||
[json| {"body": "Some real content", "smth": "here", "other": "stuff", "fake_id": 13} |] `shouldRespondWith`
|
[json| {"body": "Some real content", "smth": "here", "other": "stuff", "fake_id": 13} |] `shouldRespondWith`
|
||||||
@@ -541,6 +541,10 @@ spec actualPgVersion = do
|
|||||||
{ matchStatus = 200
|
{ matchStatus = 200
|
||||||
, matchHeaders = [] }
|
, matchHeaders = [] }
|
||||||
|
|
||||||
|
it "ignores json keys and gives 404 if no record updated" $
|
||||||
|
request methodPatch "/articles?id=eq.2001&columns=body" [("Prefer", "return=representation")]
|
||||||
|
[json| {"body": "Some real content", "smth": "here", "other": "stuff", "fake_id": 13} |] `shouldRespondWith` 404
|
||||||
|
|
||||||
describe "Row level permission" $
|
describe "Row level permission" $
|
||||||
it "set user_id when inserting rows" $ do
|
it "set user_id when inserting rows" $ do
|
||||||
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.B-lReuGNDwAlU1GOC476MlO0vAt9JNoHIlxg2vwMaO0"
|
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.B-lReuGNDwAlU1GOC476MlO0vAt9JNoHIlxg2vwMaO0"
|
||||||
|
|||||||
Reference in New Issue
Block a user