From 02c405ad809629195e04800124547ec475fcf5cd Mon Sep 17 00:00:00 2001 From: calebmer Date: Sun, 11 Oct 2015 13:16:59 -0400 Subject: [PATCH 1/9] Remove versioning feature --- CHANGELOG.md | 3 + README.md | 16 ++--- debian/postgrest.init.d | 12 ++-- src/PostgREST/App.hs | 17 +---- src/PostgREST/Config.hs | 4 +- test/Feature/StructureSpec.hs | 78 +++++++++++------------ test/SpecHelper.hs | 26 ++++---- test/Unit/PgQuerySpec.hx | 10 +-- test/Unit/PgStructureSpec.hx | 8 +-- test/fixtures/schema.sql | 116 +++++++++++++++++----------------- 10 files changed, 140 insertions(+), 150 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8badef4d0..8a4653af7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Filter columns, e.g. `?select=col1,col2` - @ruslantalpa - Does not execute the count total if header "Prefer: count=none" - @diogob +### Removed +- API versioning feature - @calebmer + ### Fixed - Tolerate a missing role in user creation - @calebmer diff --git a/README.md b/README.md index c7d8858d5..4cd691b14 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ postgrest --db-host localhost --db-port 5432 \ --db-name my_db --db-user postgres \ --db-pass foobar --db-pool 200 \ --anonymous postgres --port 3000 \ - --v1schema public + --schema public ``` In production include the `--secure` option which redirects all @@ -100,13 +100,14 @@ guide](https://github.com/begriffs/postgrest/wiki/Security-and-Permissions). ### Versioning A robust long-lived API needs the freedom to exist in multiple -versions. PostgREST supports versioning through HTTP content -negotiation. Requests for a certain version translate into switching -which database schema to search for tables. PostgreSQL schema search -paths allow tables from earlier versions to be reused verbatim in -later versions. +versions. Therefore it is a best practice that you version the database +schema exposed to PostgREST (e.g. `public1` or `api2`). This way you +future proof your API by allowing it to be backwards compatible when +you want to publish breaking API changes (e.g. a later version could +be `public2` or `api3`). -To learn more, see the [guide to versioning](https://github.com/begriffs/postgrest/wiki/API-Versioning). +For routing to different versions of a PostgREST API use a request +proxy (such as [nginx](http://nginx.org)). ### Self-documention @@ -153,7 +154,6 @@ and the [guide to routing](https://github.com/begriffs/postgrest/wiki/Routing). ### Guides * [Routing](https://github.com/begriffs/postgrest/wiki/Routing) -* [Versioning](https://github.com/begriffs/postgrest/wiki/API-Versioning) * [Performance](https://github.com/begriffs/postgrest/wiki/Performance-and-Scaling) * [Security](https://github.com/begriffs/postgrest/wiki/Security-and-Permissions) * [Tutorial](http://blog.jonharrington.org/postgrest-introduction/) (external) diff --git a/debian/postgrest.init.d b/debian/postgrest.init.d index cacf528a4..2227d8c07 100755 --- a/debian/postgrest.init.d +++ b/debian/postgrest.init.d @@ -2,12 +2,12 @@ ### BEGIN INIT INFO # Provides: postgrest # Required-Start: $local_fs $network postgresql -# Required-Stop: $local_fs $network +# Required-Stop: $local_fs $network # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Description: PostgreSQL REST API daemon ### END INIT INFO - + . /lib/lsb/init-functions if test -f /etc/default/postgrest; then . /etc/default/postgrest @@ -32,8 +32,8 @@ fi if [ -n "$POSTGREST_DBPOOL" ]; then POSTGREST_OPTS="$POSTGREST_OPTS --db-pool $POSTGREST_DBPOOL" fi -POSTGREST_OPTS="$POSTGREST_OPTS --v1schema public" - +POSTGREST_OPTS="$POSTGREST_OPTS --schema public" + start() { log_daemon_msg "Starting PostgreSQL REST API daemon" "postgrest" || true @@ -43,7 +43,7 @@ start() log_end_msg 1 || true fi } - + stop() { log_daemon_msg "Stopping PostgreSQL REST API daemon" "postgrest" || true @@ -53,7 +53,7 @@ stop() log_end_msg 1 || true fi } - + status() { status_of_proc $POSTGREST postgrest && exit 0 || exit $? diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index f9044832d..0729f337c 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -6,7 +6,6 @@ module PostgREST.App ( , isSqlError , contentTypeForAccept , jsonH -, requestedSchema , TableOptions(..) ) where @@ -29,7 +28,6 @@ import Data.Ranged.Ranges (emptyRange) import qualified Data.Set as S import Data.String.Conversions (cs) import Data.Text (Text, replace, strip) -import Text.Regex.TDFA ((=~)) import Text.Parsec.Error @@ -216,7 +214,7 @@ app dbstructure conf reqBody dbrole req = -- check that proc exists -- check that arg names are all specified - -- select * from "1".proc(a := "foo"::undefined) where whereT limit limitT + -- select * from public.proc(a := "foo"::undefined) where whereT limit limitT ([table], "PUT") -> handleJsonObj reqBody $ \obj -> do @@ -296,7 +294,7 @@ app dbstructure conf reqBody dbrole req = lookupHeader = flip lookup hdrs hasPrefer val = any (\(h,v) -> h == "Prefer" && v == val) hdrs accept = lookupHeader hAccept - schema = requestedSchema (cs $ configV1Schema conf) accept + schema = cs $ configSchema conf authenticator = cs $ configDbUser conf jwtSecret = cs $ configJwtSecret conf range = rangeRequested hdrs @@ -329,17 +327,6 @@ contentRangeH from to total = totalNotZero = fromMaybe True ((/=) 0 <$> total) fromInRange = from <= to -requestedSchema :: Text -> Maybe BS.ByteString -> Text -requestedSchema v1schema accept = - case verStr of - Just [[_, ver]] -> if ver == "1" then v1schema else cs ver - _ -> v1schema - - where - verRegex = "version[ ]*=[ ]*([0-9]+)" :: BS.ByteString - verStr = (=~ verRegex) <$> accept :: Maybe [[BS.ByteString]] - - jsonMT :: BS.ByteString jsonMT = "application/json" diff --git a/src/PostgREST/Config.hs b/src/PostgREST/Config.hs index a268f1626..03fdd05b2 100644 --- a/src/PostgREST/Config.hs +++ b/src/PostgREST/Config.hs @@ -44,7 +44,7 @@ data AppConfig = AppConfig { , configAnonRole :: String , configSecure :: Bool , configPool :: Int - , configV1Schema :: String + , configSchema :: String , configJwtSecret :: String } @@ -60,7 +60,7 @@ argParser = AppConfig <*> strOption (long "anonymous" <> short 'a' <> metavar "ROLE" <> help "postgres role to use for non-authenticated requests") <*> switch (long "secure" <> short 's' <> help "Redirect all requests to HTTPS") <*> option auto (long "db-pool" <> metavar "COUNT" <> value 10 <> help "Max connections in database pool" <> showDefault) - <*> strOption (long "v1schema" <> metavar "NAME" <> value "1" <> help "Schema to use for nonspecified version (or explicit v1)" <> showDefault) + <*> strOption (long "schema" <> short 'S' <> metavar "NAME" <> value "public" <> help "Schema to use for API routes" <> showDefault) <*> strOption (long "jwt-secret" <> metavar "SECRET" <> value "secret" <> help "Secret used to encrypt and decrypt JWT tokens)" <> showDefault) defaultCorsPolicy :: CorsResourcePolicy diff --git a/test/Feature/StructureSpec.hs b/test/Feature/StructureSpec.hs index d68db8440..187584f28 100644 --- a/test/Feature/StructureSpec.hs +++ b/test/Feature/StructureSpec.hs @@ -14,28 +14,28 @@ spec = around withApp $ do it "lists views in schema" $ request methodGet "/" [] "" `shouldRespondWith` [json| [ - {"schema":"1","name":"auto_incrementing_pk","insertable":true} - , {"schema":"1","name":"clients","insertable":true} - , {"schema":"1","name":"comments","insertable":true} - , {"schema":"1","name":"complex_items","insertable":true} - , {"schema":"1","name":"compound_pk","insertable":true} - , {"schema":"1","name":"has_count_column","insertable":false} - , {"schema":"1","name":"has_fk","insertable":true} - , {"schema":"1","name":"insertable_view_with_join","insertable":true} - , {"schema":"1","name":"items","insertable":true} - , {"schema":"1","name":"json","insertable":true} - , {"schema":"1","name":"materialized_view","insertable":false} - , {"schema":"1","name":"menagerie","insertable":true} - , {"schema":"1","name":"no_pk","insertable":true} - , {"schema":"1","name":"nullable_integer","insertable":true} - , {"schema":"1","name":"projects","insertable":true} - , {"schema":"1","name":"projects_view","insertable":true} - , {"schema":"1","name":"simple_pk","insertable":true} - , {"schema":"1","name":"tasks","insertable":true} - , {"schema":"1","name":"tsearch","insertable":true} - , {"schema":"1","name":"users","insertable":true} - , {"schema":"1","name":"users_projects","insertable":true} - , {"schema":"1","name":"users_tasks","insertable":true} + {"schema":"test","name":"auto_incrementing_pk","insertable":true} + , {"schema":"test","name":"clients","insertable":true} + , {"schema":"test","name":"comments","insertable":true} + , {"schema":"test","name":"complex_items","insertable":true} + , {"schema":"test","name":"compound_pk","insertable":true} + , {"schema":"test","name":"has_count_column","insertable":false} + , {"schema":"test","name":"has_fk","insertable":true} + , {"schema":"test","name":"insertable_view_with_join","insertable":true} + , {"schema":"test","name":"items","insertable":true} + , {"schema":"test","name":"json","insertable":true} + , {"schema":"test","name":"materialized_view","insertable":false} + , {"schema":"test","name":"menagerie","insertable":true} + , {"schema":"test","name":"no_pk","insertable":true} + , {"schema":"test","name":"nullable_integer","insertable":true} + , {"schema":"test","name":"projects","insertable":true} + , {"schema":"test","name":"projects_view","insertable":true} + , {"schema":"test","name":"simple_pk","insertable":true} + , {"schema":"test","name":"tasks","insertable":true} + , {"schema":"test","name":"tsearch","insertable":true} + , {"schema":"test","name":"users","insertable":true} + , {"schema":"test","name":"users_projects","insertable":true} + , {"schema":"test","name":"users_tasks","insertable":true} ] |] {matchStatus = 200} @@ -45,7 +45,7 @@ spec = around withApp $ do request methodGet "/" [auth] "" `shouldRespondWith` [json| [ - {"schema":"1","name":"authors_only","insertable":true} + {"schema":"test","name":"authors_only","insertable":true} ] |] {matchStatus = 200} @@ -61,7 +61,7 @@ spec = around withApp $ do "default": null, "precision": 32, "updatable": true, - "schema": "1", + "schema": "test", "name": "integer", "type": "integer", "maxLen": null, @@ -74,7 +74,7 @@ spec = around withApp $ do "default": null, "precision": 53, "updatable": true, - "schema": "1", + "schema": "test", "name": "double", "type": "double precision", "maxLen": null, @@ -86,7 +86,7 @@ spec = around withApp $ do "default": null, "precision": null, "updatable": true, - "schema": "1", + "schema": "test", "name": "varchar", "type": "character varying", "maxLen": null, @@ -99,7 +99,7 @@ spec = around withApp $ do "default": null, "precision": null, "updatable": true, - "schema": "1", + "schema": "test", "name": "boolean", "type": "boolean", "maxLen": null, @@ -111,7 +111,7 @@ spec = around withApp $ do "default": null, "precision": null, "updatable": true, - "schema": "1", + "schema": "test", "name": "date", "type": "date", "maxLen": null, @@ -123,7 +123,7 @@ spec = around withApp $ do "default": null, "precision": null, "updatable": true, - "schema": "1", + "schema": "test", "name": "money", "type": "money", "maxLen": null, @@ -136,7 +136,7 @@ spec = around withApp $ do "default": null, "precision": null, "updatable": true, - "schema": "1", + "schema": "test", "name": "enum", "type": "USER-DEFINED", "maxLen": null, @@ -166,7 +166,7 @@ spec = around withApp $ do "default":null, "precision":64, "updatable":false, - "schema":"1", + "schema":"test", "name":"id", "type":"bigint", "maxLen":null, @@ -182,7 +182,7 @@ spec = around withApp $ do "default":null, "precision":32, "updatable":false, - "schema":"1", + "schema":"test", "name":"auto_inc_fk", "type":"integer", "maxLen":null, @@ -198,7 +198,7 @@ spec = around withApp $ do "default":null, "precision":null, "updatable":false, - "schema":"1", + "schema":"test", "name":"simple_fk", "type":"character varying", "maxLen":255, @@ -211,7 +211,7 @@ spec = around withApp $ do "default":null, "precision":null, "updatable":false, - "schema":"1", + "schema":"test", "name":"nullable_string", "type":"character varying", "maxLen":null, @@ -224,7 +224,7 @@ spec = around withApp $ do "default":null, "precision":null, "updatable":false, - "schema":"1", + "schema":"test", "name":"non_nullable_string", "type":"character varying", "maxLen":null, @@ -237,7 +237,7 @@ spec = around withApp $ do "default":null, "precision":null, "updatable":false, - "schema":"1", + "schema":"test", "name":"inserted_at", "type":"timestamp with time zone", "maxLen":null, @@ -261,7 +261,7 @@ spec = around withApp $ do "default": "nextval('\"1\".has_fk_id_seq'::regclass)", "precision": 64, "updatable": true, - "schema": "1", + "schema": "test", "name": "id", "type": "bigint", "maxLen": null, @@ -273,7 +273,7 @@ spec = around withApp $ do "default": null, "precision": 32, "updatable": true, - "schema": "1", + "schema": "test", "name": "auto_inc_fk", "type": "integer", "maxLen": null, @@ -285,7 +285,7 @@ spec = around withApp $ do "default": null, "precision": null, "updatable": true, - "schema": "1", + "schema": "test", "name": "simple_fk", "type": "character varying", "maxLen": 255, diff --git a/test/SpecHelper.hs b/test/SpecHelper.hs index f47f23b6d..1587dabd3 100644 --- a/test/SpecHelper.hs +++ b/test/SpecHelper.hs @@ -38,7 +38,7 @@ isLeft (Left _ ) = True isLeft _ = False cfg :: AppConfig -cfg = AppConfig "postgrest_test" 5432 "postgrest_test" "" "localhost" 3000 "postgrest_anonymous" False 10 "1" "safe" +cfg = AppConfig "postgrest_test" 5432 "postgrest_test" "" "localhost" 3000 "postgrest_anonymous" False 10 "test" "safe" testPoolOpts :: PoolSettings testPoolOpts = fromMaybe (error "bad settings") $ H.poolSettings 1 30 @@ -66,7 +66,7 @@ withApp perform = do dbstructure <- case metadata of Left e -> fail $ show e Right (tabs, rels, cols, keys) -> - return $ DbStructure { + return DbStructure { tables=tabs , columns=cols , relations=rels @@ -88,7 +88,7 @@ resetDb = do <- H.acquirePool pgSettings testPoolOpts void . liftIO $ H.session pool $ H.tx Nothing $ do - H.unitEx [H.stmt| drop schema if exists "1" cascade |] + H.unitEx [H.stmt| drop schema if exists test cascade |] H.unitEx [H.stmt| drop schema if exists private cascade |] H.unitEx [H.stmt| drop schema if exists postgrest cascade |] @@ -129,7 +129,7 @@ clearTable :: Text -> IO () clearTable table = do pool <- testPool void . liftIO $ H.session pool $ H.tx Nothing $ - H.unitEx $ B.Stmt ("delete from \"1\"."<>table) V.empty True + H.unitEx $ B.Stmt ("delete from test."<>table) V.empty True createItems :: Int -> IO () createItems n = do @@ -137,7 +137,7 @@ createItems n = do void . liftIO $ H.session pool $ H.tx Nothing txn where txn = mapM_ H.unitEx stmts - stmts = map [H.stmt|insert into "1".items (id) values (?)|] [1..n] + stmts = map [H.stmt|insert into test.items (id) values (?)|] [1..n] createComplexItems :: IO () createComplexItems = do @@ -145,11 +145,11 @@ createComplexItems = do void . liftIO $ H.session pool $ H.tx Nothing txn where txn = mapM_ H.unitEx stmts - stmts = getZipList $ [H.stmt|insert into "1".complex_items (id, name, settings) values (?,?,?)|] + stmts = getZipList $ [H.stmt|insert into test.complex_items (id, name, settings) values (?,?,?)|] <$> ZipList ([1..3]::[Int]) <*> ZipList (["One", "Two", "Three"]::[Text]) - <*> ZipList ([jobj,jobj,jobj]) - jobj = (J.object [("foo", J.object [("int", J.Number 1),("bar", J.String "baz")])]) + <*> ZipList [jobj,jobj,jobj] + jobj = J.object [("foo", J.object [("int", J.Number 1),("bar", J.String "baz")])] createNulls :: Int -> IO () createNulls n = do @@ -157,14 +157,14 @@ createNulls n = do void . liftIO $ H.session pool $ H.tx Nothing txn where txn = mapM_ H.unitEx (stmt':stmts) - stmt' = [H.stmt|insert into "1".no_pk (a,b) values (null,null)|] - stmts = map [H.stmt|insert into "1".no_pk (a,b) values (?,0)|] [1..n] + stmt' = [H.stmt|insert into test.no_pk (a,b) values (null,null)|] + stmts = map [H.stmt|insert into test.no_pk (a,b) values (?,0)|] [1..n] createNullInteger :: IO () createNullInteger = do pool <- testPool void . liftIO $ H.session pool $ H.tx Nothing $ - H.unitEx $ [H.stmt| insert into "1".nullable_integer (a) values (null) |] + H.unitEx $ [H.stmt| insert into "test".nullable_integer (a) values (null) |] createLikableStrings :: IO () createLikableStrings = do @@ -174,7 +174,7 @@ createLikableStrings = do H.unitEx $ insertSimplePk "xYYx" "v" where insertSimplePk :: Text -> Text -> H.Stmt P.Postgres - insertSimplePk = [H.stmt|insert into "1".simple_pk (k, extra) values (?,?)|] + insertSimplePk = [H.stmt|insert into test.simple_pk (k, extra) values (?,?)|] createJsonData :: IO () createJsonData = do @@ -182,6 +182,6 @@ createJsonData = do void . liftIO $ H.session pool $ H.tx Nothing $ H.unitEx $ [H.stmt| - insert into "1".json (data) values (?) + insert into test.json (data) values (?) |] (J.object [("foo", J.object [("bar", J.String "baz")])]) diff --git a/test/Unit/PgQuerySpec.hx b/test/Unit/PgQuerySpec.hx index 0cc3de225..5842fe1ef 100644 --- a/test/Unit/PgQuerySpec.hx +++ b/test/Unit/PgQuerySpec.hx @@ -32,7 +32,7 @@ spec = around dbWithSchema $ do describe "insert" $ describe "with an auto-increment key" $ do it "inserts and responds with a full object description" $ \conn -> do - r <- insert "1" "auto_incrementing_pk" (SqlRow [ + r <- insert "test" "auto_incrementing_pk" (SqlRow [ ("non_nullable_string", toSql ("a string"::String))]) conn let returnRow = incFromList . toList $ r incStr returnRow `shouldBe` "a string" @@ -43,19 +43,19 @@ spec = around dbWithSchema $ do [returnRow] `shouldBe` map incFromList tRows it "throws an exception if the PK is not unique" $ \conn -> do - r <- insert "1" "auto_incrementing_pk" (SqlRow [ + r <- insert "test" "auto_incrementing_pk" (SqlRow [ ("non_nullable_string", toSql ("a string"::String))]) conn let row = SqlRow . map (Control.Arrow.first cs) . toList $ r - insert "1" "auto_incrementing_pk" row conn `shouldThrow` \e -> + insert "test" "auto_incrementing_pk" row conn `shouldThrow` \e -> seState e == "23505" -- uniqueness violation code it "throws an exception if a required value is missing" $ \conn -> - insert "1" "auto_incrementing_pk" (SqlRow [ + insert "test" "auto_incrementing_pk" (SqlRow [ ("nullable_string", toSql ("a string"::String))]) conn `shouldThrow` \e -> seState e == "23502" it "generates a default values query if no data is provided" $ \c -> do - r <- insert "1" "items" (SqlRow []) c + r <- insert "test" "items" (SqlRow []) c let [row] = toList r quickALQuery c "select * from \"1\".items where id = ?" [snd row] `shouldReturn` [[row]] diff --git a/test/Unit/PgStructureSpec.hx b/test/Unit/PgStructureSpec.hx index 0ca6279fb..b1c1570dd 100644 --- a/test/Unit/PgStructureSpec.hx +++ b/test/Unit/PgStructureSpec.hx @@ -12,25 +12,25 @@ spec :: Spec spec = around dbWithSchema $ beforeWith setRole $ do describe "tables" $ it "shows all the tables" $ \conn -> do - ts <- tables "1" conn + ts <- tables "test" conn map tableName ts `shouldBe` ["authors_only","auto_incrementing_pk", "compound_pk","has_fk","insertable_view_with_join","items","menagerie","no_pk", "simple_pk"] describe "columns" $ do it "responds with each column for the table" $ \conn -> do - cs <- columns "1" "auto_incrementing_pk" conn + cs <- columns "test" "auto_incrementing_pk" conn map colName cs `shouldBe` ["id","nullable_string","non_nullable_string", "inserted_at"] it "includes foreign key data" $ \conn -> do - cs <- columns "1" "has_fk" conn + cs <- columns "test" "has_fk" conn map colFK cs `shouldBe` [Nothing, Just $ ForeignKey "auto_incrementing_pk" "id", Just $ ForeignKey "simple_pk" "k"] describe "foreignKeys" $ it "has a description of the foreign key columns" $ \conn -> - foreignKeys "1" "has_fk" conn `shouldReturn` M.fromList [ + foreignKeys "test" "has_fk" conn `shouldReturn` M.fromList [ ("auto_inc_fk", ForeignKey {fkTable="auto_incrementing_pk", fkCol="id"}), ("simple_fk", ForeignKey { fkTable="simple_pk", fkCol="k"})] diff --git a/test/fixtures/schema.sql b/test/fixtures/schema.sql index b69e45d38..2066c4f68 100755 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -5,10 +5,10 @@ SET check_function_bodies = false; SET client_min_messages = warning; -CREATE SCHEMA "1"; +CREATE SCHEMA test; -ALTER SCHEMA "1" OWNER TO postgrest_test; +ALTER SCHEMA test OWNER TO postgrest_test; CREATE SCHEMA postgrest; @@ -30,7 +30,7 @@ CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; -SET search_path = "1", pg_catalog; +SET search_path = test, pg_catalog; CREATE TYPE enum_menagerie_type AS ENUM ( @@ -39,7 +39,7 @@ CREATE TYPE enum_menagerie_type AS ENUM ( ); -ALTER TYPE "1".enum_menagerie_type OWNER TO postgrest_test; +ALTER TYPE test.enum_menagerie_type OWNER TO postgrest_test; SET search_path = postgrest, pg_catalog; @@ -83,18 +83,18 @@ $$; ALTER FUNCTION postgrest.set_authors_only_owner() OWNER TO postgrest_test; -CREATE FUNCTION "1".insert_insertable_view_with_join() RETURNS trigger +CREATE FUNCTION test.insert_insertable_view_with_join() RETURNS trigger LANGUAGE plpgsql AS $$ begin - INSERT INTO "1".auto_incrementing_pk (nullable_string, non_nullable_string) VALUES (NEW.nullable_string, NEW.non_nullable_string); + INSERT INTO test.auto_incrementing_pk (nullable_string, non_nullable_string) VALUES (NEW.nullable_string, NEW.non_nullable_string); RETURN NEW; end; $$; -ALTER FUNCTION "1".insert_insertable_view_with_join() OWNER TO postgrest_test; +ALTER FUNCTION test.insert_insertable_view_with_join() OWNER TO postgrest_test; -SET search_path = "1", pg_catalog; +SET search_path = test, pg_catalog; SET default_tablespace = ''; @@ -107,7 +107,7 @@ CREATE TABLE authors_only ( ); -ALTER TABLE "1".authors_only OWNER TO postgrest_test_author; +ALTER TABLE test.authors_only OWNER TO postgrest_test_author; CREATE TABLE auto_incrementing_pk ( @@ -118,7 +118,7 @@ CREATE TABLE auto_incrementing_pk ( ); -ALTER TABLE "1".auto_incrementing_pk OWNER TO postgrest_test; +ALTER TABLE test.auto_incrementing_pk OWNER TO postgrest_test; CREATE SEQUENCE auto_incrementing_pk_id_seq @@ -129,7 +129,7 @@ CREATE SEQUENCE auto_incrementing_pk_id_seq CACHE 1; -ALTER TABLE "1".auto_incrementing_pk_id_seq OWNER TO postgrest_test; +ALTER TABLE test.auto_incrementing_pk_id_seq OWNER TO postgrest_test; ALTER SEQUENCE auto_incrementing_pk_id_seq OWNED BY auto_incrementing_pk.id; @@ -143,7 +143,7 @@ CREATE TABLE compound_pk ( ); -ALTER TABLE "1".compound_pk OWNER TO postgrest_test; +ALTER TABLE test.compound_pk OWNER TO postgrest_test; CREATE TABLE has_fk ( @@ -153,7 +153,7 @@ CREATE TABLE has_fk ( ); -ALTER TABLE "1".has_fk OWNER TO postgrest_test; +ALTER TABLE test.has_fk OWNER TO postgrest_test; CREATE SEQUENCE has_fk_id_seq @@ -164,18 +164,18 @@ CREATE SEQUENCE has_fk_id_seq CACHE 1; -ALTER TABLE "1".has_fk_id_seq OWNER TO postgrest_test; +ALTER TABLE test.has_fk_id_seq OWNER TO postgrest_test; ALTER SEQUENCE has_fk_id_seq OWNED BY has_fk.id; -CREATE MATERIALIZED VIEW "1".materialized_view AS +CREATE MATERIALIZED VIEW test.materialized_view AS SELECT version(); -ALTER TABLE "1".materialized_view OWNER TO postgrest_test; +ALTER TABLE test.materialized_view OWNER TO postgrest_test; -CREATE VIEW "1".insertable_view_with_join AS +CREATE VIEW test.insertable_view_with_join AS SELECT has_fk.id, has_fk.auto_inc_fk, has_fk.simple_fk, @@ -186,12 +186,12 @@ CREATE VIEW "1".insertable_view_with_join AS JOIN auto_incrementing_pk USING (id)); -ALTER TABLE "1".insertable_view_with_join OWNER TO postgrest_test; +ALTER TABLE test.insertable_view_with_join OWNER TO postgrest_test; -CREATE VIEW "1".has_count_column AS +CREATE VIEW test.has_count_column AS SELECT 1 AS count; -ALTER TABLE "1".insertable_view_with_join OWNER TO postgrest_test; +ALTER TABLE test.insertable_view_with_join OWNER TO postgrest_test; CREATE TABLE items ( @@ -199,7 +199,7 @@ CREATE TABLE items ( ); -ALTER TABLE "1".items OWNER TO postgrest_test; +ALTER TABLE test.items OWNER TO postgrest_test; CREATE TABLE complex_items ( id bigint NOT NULL, @@ -208,41 +208,41 @@ CREATE TABLE complex_items ( ); -ALTER TABLE "1".complex_items OWNER TO postgrest_test; +ALTER TABLE test.complex_items OWNER TO postgrest_test; --- Structure for testing table relations CREATE TABLE clients( id INT PRIMARY KEY NOT NULL, name TEXT NOT NULL ); -ALTER TABLE "1".clients OWNER TO postgrest_test; +ALTER TABLE test.clients OWNER TO postgrest_test; CREATE TABLE projects( id INT PRIMARY KEY NOT NULL, name TEXT NOT NULL, client_id INT REFERENCES clients(id) ); -ALTER TABLE "1".projects OWNER TO postgrest_test; +ALTER TABLE test.projects OWNER TO postgrest_test; CREATE TABLE tasks( id INT PRIMARY KEY NOT NULL, name TEXT NOT NULL, project_id INT REFERENCES projects(id) ); -ALTER TABLE "1".tasks OWNER TO postgrest_test; +ALTER TABLE test.tasks OWNER TO postgrest_test; CREATE TABLE users( id INT PRIMARY KEY NOT NULL, name TEXT NOT NULL ); -ALTER TABLE "1".users OWNER TO postgrest_test; +ALTER TABLE test.users OWNER TO postgrest_test; CREATE TABLE users_tasks( user_id INT REFERENCES users(id), task_id INT REFERENCES tasks(id), CONSTRAINT task_user PRIMARY KEY (task_id,user_id) ); -ALTER TABLE "1".users_tasks OWNER TO postgrest_test; +ALTER TABLE test.users_tasks OWNER TO postgrest_test; CREATE TABLE comments( id INT PRIMARY KEY NOT NULL, @@ -252,22 +252,22 @@ task_id INT NOT NULL, content TEXT NOT NULL, FOREIGN KEY (task_id,user_id) REFERENCES users_tasks (task_id,user_id) ); -ALTER TABLE "1".comments OWNER TO postgrest_test; +ALTER TABLE test.comments OWNER TO postgrest_test; CREATE TABLE users_projects( user_id INT REFERENCES users(id), project_id INT REFERENCES projects(id), CONSTRAINT project_user PRIMARY KEY (project_id, user_id) ); -ALTER TABLE "1".users_projects OWNER TO postgrest_test; +ALTER TABLE test.users_projects OWNER TO postgrest_test; -CREATE VIEW "1".projects_view AS +CREATE VIEW test.projects_view AS SELECT projects.id, projects.name, projects.client_id FROM projects; -ALTER TABLE "1".projects_view OWNER TO postgrest_test; +ALTER TABLE test.projects_view OWNER TO postgrest_test; ------- SAMPLE DATA ----- INSERT INTO clients VALUES (1, 'Microsoft'),(2, 'Apple'); INSERT INTO projects VALUES (1,'Windows 7', 1),(2,'Windows 10', 1),(3,'IOS', 2),(4,'OSX', 2); @@ -286,25 +286,25 @@ CREATE SEQUENCE items_id_seq CACHE 1; -ALTER TABLE "1".items_id_seq OWNER TO postgrest_test; +ALTER TABLE test.items_id_seq OWNER TO postgrest_test; ALTER SEQUENCE items_id_seq OWNED BY items.id; -CREATE FUNCTION "1".getitemrange(min bigint, max bigint) RETURNS SETOF "1".items AS $$ - SELECT * FROM "1".items WHERE id > $1 AND id <= $2; +CREATE FUNCTION test.getitemrange(min bigint, max bigint) RETURNS SETOF test.items AS $$ + SELECT * FROM test.items WHERE id > $1 AND id <= $2; $$ LANGUAGE SQL; -CREATE FUNCTION "1".sayhello(name text) RETURNS text AS $$ +CREATE FUNCTION test.sayhello(name text) RETURNS text AS $$ SELECT 'Hello, ' || $1; $$ LANGUAGE SQL; -CREATE FUNCTION "1".problem() RETURNS void LANGUAGE plpgsql AS +CREATE FUNCTION test.problem() RETURNS void LANGUAGE plpgsql AS $$ BEGIN RAISE 'bad thing'; @@ -323,7 +323,7 @@ CREATE TABLE menagerie ( ); -ALTER TABLE "1".menagerie OWNER TO postgrest_test; +ALTER TABLE test.menagerie OWNER TO postgrest_test; CREATE TABLE no_pk ( @@ -332,7 +332,7 @@ CREATE TABLE no_pk ( ); -ALTER TABLE "1".no_pk OWNER TO postgrest_test; +ALTER TABLE test.no_pk OWNER TO postgrest_test; CREATE TABLE nullable_integer ( @@ -340,7 +340,7 @@ CREATE TABLE nullable_integer ( ); -ALTER TABLE "1".nullable_integer OWNER TO postgrest_test; +ALTER TABLE test.nullable_integer OWNER TO postgrest_test; CREATE TABLE simple_pk ( @@ -349,7 +349,7 @@ CREATE TABLE simple_pk ( ); -ALTER TABLE "1".simple_pk OWNER TO postgrest_test; +ALTER TABLE test.simple_pk OWNER TO postgrest_test; CREATE TABLE json @@ -358,14 +358,14 @@ CREATE TABLE json ); -ALTER TABLE "1".json OWNER TO postgrest_test; +ALTER TABLE test.json OWNER TO postgrest_test; CREATE TABLE tsearch ( text_search_vector tsvector ); -ALTER TABLE "1".tsearch OWNER TO postgrest_test; +ALTER TABLE test.tsearch OWNER TO postgrest_test; SET search_path = postgrest, pg_catalog; @@ -406,7 +406,7 @@ ALTER TABLE private.articles_id_seq OWNER TO postgrest_test; ALTER SEQUENCE articles_id_seq OWNED BY articles.id; -SET search_path = "1", pg_catalog; +SET search_path = test, pg_catalog; ALTER TABLE ONLY auto_incrementing_pk ALTER COLUMN id SET DEFAULT nextval('auto_incrementing_pk_id_seq'::regclass); @@ -426,7 +426,7 @@ SET search_path = private, pg_catalog; ALTER TABLE ONLY articles ALTER COLUMN id SET DEFAULT nextval('articles_id_seq'::regclass); -SET search_path = "1", pg_catalog; +SET search_path = test, pg_catalog; @@ -471,20 +471,20 @@ SET search_path = private, pg_catalog; SELECT pg_catalog.setval('articles_id_seq', 1, false); -SET search_path = "1", pg_catalog; +SET search_path = test, pg_catalog; -CREATE FUNCTION public.always_true("1".items) RETURNS boolean +CREATE FUNCTION public.always_true(test.items) RETURNS boolean LANGUAGE sql STABLE AS $$ SELECT true $$; -ALTER FUNCTION public.always_true("1".items) OWNER TO postgrest_test; +ALTER FUNCTION public.always_true(test.items) OWNER TO postgrest_test; ALTER TABLE ONLY authors_only ADD CONSTRAINT authors_only_pkey PRIMARY KEY (secret); -CREATE TRIGGER insert_insertable_view_with_join INSTEAD OF INSERT ON "1".insertable_view_with_join FOR EACH ROW EXECUTE PROCEDURE "1".insert_insertable_view_with_join(); +CREATE TRIGGER insert_insertable_view_with_join INSTEAD OF INSERT ON test.insertable_view_with_join FOR EACH ROW EXECUTE PROCEDURE test.insert_insertable_view_with_join(); CREATE TRIGGER secrets_owner_track BEFORE INSERT OR UPDATE ON authors_only FOR EACH ROW EXECUTE PROCEDURE postgrest.set_authors_only_owner(); @@ -547,7 +547,7 @@ SET search_path = private, pg_catalog; CREATE TRIGGER articles_owner_track BEFORE INSERT OR UPDATE ON articles FOR EACH ROW EXECUTE PROCEDURE postgrest.update_owner(); -SET search_path = "1", pg_catalog; +SET search_path = test, pg_catalog; ALTER TABLE ONLY has_fk @@ -560,11 +560,11 @@ ALTER TABLE ONLY has_fk -REVOKE ALL ON SCHEMA "1" FROM PUBLIC; -REVOKE ALL ON SCHEMA "1" FROM postgrest_test; -GRANT ALL ON SCHEMA "1" TO postgrest_test; -GRANT USAGE ON SCHEMA "1" TO postgrest_anonymous; -GRANT USAGE ON SCHEMA "1" TO postgrest_test_author; +REVOKE ALL ON SCHEMA test FROM PUBLIC; +REVOKE ALL ON SCHEMA test FROM postgrest_test; +GRANT ALL ON SCHEMA test TO postgrest_test; +GRANT USAGE ON SCHEMA test TO postgrest_anonymous; +GRANT USAGE ON SCHEMA test TO postgrest_test_author; @@ -737,10 +737,10 @@ REVOKE ALL ON TABLE has_count_column FROM postgrest_test; GRANT ALL ON TABLE has_count_column TO postgrest_test; GRANT ALL ON TABLE has_count_column TO postgrest_anonymous; -REVOKE ALL ON FUNCTION public.always_true("1".items) FROM PUBLIC; -REVOKE ALL ON FUNCTION public.always_true("1".items) FROM postgrest_test; -GRANT ALL ON FUNCTION public.always_true("1".items) TO postgrest_test; -GRANT ALL ON FUNCTION public.always_true("1".items) TO postgrest_anonymous; +REVOKE ALL ON FUNCTION public.always_true(test.items) FROM PUBLIC; +REVOKE ALL ON FUNCTION public.always_true(test.items) FROM postgrest_test; +GRANT ALL ON FUNCTION public.always_true(test.items) TO postgrest_test; +GRANT ALL ON FUNCTION public.always_true(test.items) TO postgrest_anonymous; SET search_path = postgrest, pg_catalog; From 5feb334191cc8c278b40c72f0028f8770b0b79c4 Mon Sep 17 00:00:00 2001 From: calebmer Date: Sun, 11 Oct 2015 16:26:29 -0400 Subject: [PATCH 2/9] Use postgres connection string --- src/PostgREST/App.hs | 5 ++--- src/PostgREST/Config.hs | 29 ++++++++++------------------- src/PostgREST/Main.hs | 15 +++++++-------- src/PostgREST/Middleware.hs | 9 ++++----- test/SpecHelper.hs | 18 +++++++++++------- 5 files changed, 34 insertions(+), 42 deletions(-) diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index 0729f337c..916098229 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -57,8 +57,8 @@ import PostgREST.Types import Prelude -app :: DbStructure -> AppConfig -> BL.ByteString -> DbRole -> Request -> H.Tx P.Postgres s Response -app dbstructure conf reqBody dbrole req = +app :: DbStructure -> AppConfig -> Text -> BL.ByteString -> DbRole -> Request -> H.Tx P.Postgres s Response +app dbstructure conf authenticator reqBody dbrole req = case (path, verb) of ([], _) -> do @@ -295,7 +295,6 @@ app dbstructure conf reqBody dbrole req = hasPrefer val = any (\(h,v) -> h == "Prefer" && v == val) hdrs accept = lookupHeader hAccept schema = cs $ configSchema conf - authenticator = cs $ configDbUser conf jwtSecret = cs $ configJwtSecret conf range = rangeRequested hdrs allOrigins = ("Access-Control-Allow-Origin", "*") :: Header diff --git a/src/PostgREST/Config.hs b/src/PostgREST/Config.hs index 03fdd05b2..e57ce7128 100644 --- a/src/PostgREST/Config.hs +++ b/src/PostgREST/Config.hs @@ -34,34 +34,25 @@ import Prelude -- | Data type to store all command line options data AppConfig = AppConfig { - configDbName :: String - , configDbPort :: Int - , configDbUser :: String - , configDbPass :: String - , configDbHost :: String - + configDatabase :: String , configPort :: Int , configAnonRole :: String - , configSecure :: Bool - , configPool :: Int , configSchema :: String + , configSecure :: Bool , configJwtSecret :: String + , configPool :: Int } argParser :: Parser AppConfig argParser = AppConfig - <$> strOption (long "db-name" <> short 'd' <> metavar "NAME" <> help "name of database") - <*> option auto (long "db-port" <> short 'P' <> metavar "PORT" <> value 5432 <> help "postgres server port" <> showDefault) - <*> strOption (long "db-user" <> short 'U' <> metavar "ROLE" <> help "postgres authenticator role") - <*> strOption (long "db-pass" <> metavar "PASS" <> value "" <> help "password for authenticator role") - <*> strOption (long "db-host" <> metavar "HOST" <> value "localhost" <> help "postgres server hostname" <> showDefault) + <$> argument str (help "database connection string" <> metavar "URL") - <*> option auto (long "port" <> short 'p' <> metavar "PORT" <> value 3000 <> help "port number on which to run HTTP server" <> showDefault) - <*> strOption (long "anonymous" <> short 'a' <> metavar "ROLE" <> help "postgres role to use for non-authenticated requests") - <*> switch (long "secure" <> short 's' <> help "Redirect all requests to HTTPS") - <*> option auto (long "db-pool" <> metavar "COUNT" <> value 10 <> help "Max connections in database pool" <> showDefault) - <*> strOption (long "schema" <> short 'S' <> metavar "NAME" <> value "public" <> help "Schema to use for API routes" <> showDefault) - <*> strOption (long "jwt-secret" <> metavar "SECRET" <> value "secret" <> help "Secret used to encrypt and decrypt JWT tokens)" <> showDefault) + <*> option auto (long "port" <> short 'p' <> help "port number on which to run HTTP server" <> metavar "PORT" <> value 3000 <> showDefault) + <*> strOption (long "anonymous" <> short 'a' <> help "postgres role to use for non-authenticated requests" <> metavar "ROLE") + <*> strOption (long "schema" <> short 'S' <> help "schema to use for API routes" <> metavar "NAME" <> value "1" <> showDefault) + <*> switch (long "secure" <> short 's' <> help "redirect all requests to HTTPS") + <*> strOption (long "jwt-secret" <> short 'j' <> help "secret used to encrypt and decrypt JWT tokens" <> metavar "SECRET" <> value "secret" <> showDefault) + <*> option auto (long "pool" <> short 'o' <> help "max connections in database pool" <> metavar "COUNT" <> value 10 <> showDefault) defaultCorsPolicy :: CorsResourcePolicy defaultCorsPolicy = CorsResourcePolicy Nothing diff --git a/src/PostgREST/Main.hs b/src/PostgREST/Main.hs index 2c1567c51..180bde91a 100644 --- a/src/PostgREST/Main.hs +++ b/src/PostgREST/Main.hs @@ -31,7 +31,7 @@ import PostgREST.Config (AppConfig (..), isServerVersionSupported :: H.Session P.Postgres IO Bool isServerVersionSupported = do - Identity (row :: Text) <- H.tx Nothing $ H.singleEx $ [H.stmt|SHOW server_version_num|] + Identity (row :: Text) <- H.tx Nothing $ H.singleEx [H.stmt|SHOW server_version_num|] return $ read (cs row) >= minimumPgVersion main :: IO () @@ -50,11 +50,7 @@ main = do Prelude.putStrLn $ "Listening on port " ++ (show $ configPort conf :: String) - let pgSettings = P.ParamSettings (cs $ configDbHost conf) - (fromIntegral $ configDbPort conf) - (cs $ configDbUser conf) - (cs $ configDbPass conf) - (cs $ configDbName conf) + let pgSettings = P.StringSettings $ cs (configDatabase conf) appSettings = setPort port . setServerName (cs $ "postgrest/" <> prettyVersion) $ defaultSettings @@ -71,6 +67,10 @@ main = do fail "Cannot run in this PostgreSQL version, PostgREST needs at least 9.2.0" ) supportedOrError + Right authenticator <- H.session pool $ do + Identity (role :: Text) <- H.tx Nothing $ H.singleEx [H.stmt|SELECT SESSION_USER|] + return role + let txSettings = Just (H.ReadCommitted, Just True) metadata <- H.session pool $ H.tx txSettings $ do tabs <- allTables @@ -89,9 +89,8 @@ main = do , primaryKeys=keys } - runSettings appSettings $ middle $ \ req respond -> do body <- strictRequestBody req resOrError <- liftIO $ H.session pool $ H.tx txSettings $ - authenticated conf (app dbstructure conf body) req + authenticated conf authenticator (app dbstructure conf authenticator body) req either (respond . errResponse) respond resOrError diff --git a/src/PostgREST/Middleware.hs b/src/PostgREST/Middleware.hs index b2dfee16a..e841c592c 100644 --- a/src/PostgREST/Middleware.hs +++ b/src/PostgREST/Middleware.hs @@ -33,22 +33,21 @@ import PostgREST.Config (AppConfig (..), corsPolicy) import Prelude -authenticated :: forall s. AppConfig -> +authenticated :: forall s. AppConfig -> Text -> (DbRole -> Request -> H.Tx P.Postgres s Response) -> Request -> H.Tx P.Postgres s Response -authenticated conf app req = do +authenticated conf authenticator app req = do attempt <- httpRequesterRole (requestHeaders req) case attempt of MalformedAuth -> return $ responseLBS status400 [] "Malformed basic auth header" LoginFailed -> return $ responseLBS status401 [] "Invalid username or password" - LoginSuccess role uid -> if role /= currentRole then runInRole role uid else app currentRole req - NoCredentials -> if anon /= currentRole then runInRole anon "" else app currentRole req + LoginSuccess role uid -> if role /= authenticator then runInRole role uid else app authenticator req + NoCredentials -> if anon /= authenticator then runInRole anon "" else app authenticator req where jwtSecret = cs $ configJwtSecret conf - currentRole = cs $ configDbUser conf anon = cs $ configAnonRole conf httpRequesterRole :: RequestHeaders -> H.Tx P.Postgres s LoginAttempt httpRequesterRole hdrs = do diff --git a/test/SpecHelper.hs b/test/SpecHelper.hs index 1587dabd3..c4b8727cc 100644 --- a/test/SpecHelper.hs +++ b/test/SpecHelper.hs @@ -20,6 +20,7 @@ import Network.HTTP.Types.Header (Header, ByteRange, renderByteRange, import Codec.Binary.Base64.String (encode) import Data.CaseInsensitive (CI(..)) import Data.Maybe (fromMaybe) +import Data.Functor.Identity import Text.Regex.TDFA ((=~)) import qualified Data.ByteString.Char8 as BS import System.Process (readProcess) @@ -33,28 +34,31 @@ import PostgREST.Error(errResponse) import PostgREST.PgStructure import PostgREST.Types +dbString :: String +dbString = "postgres://postgrest_test@localhost:5432/postgrest_test" + isLeft :: Either a b -> Bool isLeft (Left _ ) = True isLeft _ = False cfg :: AppConfig -cfg = AppConfig "postgrest_test" 5432 "postgrest_test" "" "localhost" 3000 "postgrest_anonymous" False 10 "test" "safe" +cfg = AppConfig dbString 3000 "postgrest_anonymous" "test" False "safe" 10 testPoolOpts :: PoolSettings testPoolOpts = fromMaybe (error "bad settings") $ H.poolSettings 1 30 pgSettings :: P.Settings -pgSettings = P.ParamSettings (cs $ configDbHost cfg) - (fromIntegral $ configDbPort cfg) - (cs $ configDbUser cfg) - (cs $ configDbPass cfg) - (cs $ configDbName cfg) +pgSettings = P.StringSettings $ cs dbString withApp :: ActionWith Application -> IO () withApp perform = do pool :: H.Pool P.Postgres <- H.acquirePool pgSettings testPoolOpts + Right authenticator <- H.session pool $ do + Identity (role :: Text) <- H.tx Nothing $ H.singleEx [H.stmt|SELECT SESSION_USER|] + return role + let txSettings = Just (H.ReadCommitted, Just True) metadata <- H.session pool $ H.tx txSettings $ do tabs <- allTables @@ -76,7 +80,7 @@ withApp perform = do perform $ middle $ \req resp -> do body <- strictRequestBody req result <- liftIO $ H.session pool $ H.tx txSettings - $ authenticated cfg (app dbstructure cfg body) req + $ authenticated cfg authenticator (app dbstructure cfg authenticator body) req either (resp . errResponse) resp result where middle = defaultMiddle False From f6aa93f09437c99fd2bac188be8d5901595ca5e6 Mon Sep 17 00:00:00 2001 From: calebmer Date: Sun, 11 Oct 2015 16:46:51 -0400 Subject: [PATCH 3/9] Add details to changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a4653af7..b52223031 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,11 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Embed associations, e.g. `/film?select=*,director(*)` - @ruslantalpa - Filter columns, e.g. `?select=col1,col2` - @ruslantalpa - Does not execute the count total if header "Prefer: count=none" - @diogob +- Postgres connection string argument - @calebmer ### Removed - API versioning feature - @calebmer +- `--db-x` command line arguments - @calebmer ### Fixed - Tolerate a missing role in user creation - @calebmer From de43ac52c4f55cc2fffdb8517b569c938800e0e1 Mon Sep 17 00:00:00 2001 From: calebmer Date: Sun, 11 Oct 2015 16:47:09 -0400 Subject: [PATCH 4/9] Rename connection string metavar --- src/PostgREST/Config.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PostgREST/Config.hs b/src/PostgREST/Config.hs index e57ce7128..b83768da8 100644 --- a/src/PostgREST/Config.hs +++ b/src/PostgREST/Config.hs @@ -45,7 +45,7 @@ data AppConfig = AppConfig { argParser :: Parser AppConfig argParser = AppConfig - <$> argument str (help "database connection string" <> metavar "URL") + <$> argument str (help "database connection string" <> metavar "STRING") <*> option auto (long "port" <> short 'p' <> help "port number on which to run HTTP server" <> metavar "PORT" <> value 3000 <> showDefault) <*> strOption (long "anonymous" <> short 'a' <> help "postgres role to use for non-authenticated requests" <> metavar "ROLE") From fdcf074dfd518c09958e644a6654f46da4c10e6e Mon Sep 17 00:00:00 2001 From: calebmer Date: Sun, 11 Oct 2015 16:50:47 -0400 Subject: [PATCH 5/9] Update readme --- README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4cd691b14..dd8d92c10 100644 --- a/README.md +++ b/README.md @@ -24,13 +24,16 @@ your own projects. Download the binary ([latest release](https://github.com/begriffs/postgrest/releases/latest)) and invoke like so: ```bash -postgrest --db-host localhost --db-port 5432 \ - --db-name my_db --db-user postgres \ - --db-pass foobar --db-pool 200 \ - --anonymous postgres --port 3000 \ - --schema public +postgrest postgres://postgres:foobar@localhost:5432/my_db + --port 3000 \ + --schema public \ + --anonymous postgres \ + --pool 200 ``` +For more information on valid connection strings see the +[Postgres docs](http://www.postgresql.org/docs/9.4/static/libpq-connect.html#LIBPQ-CONNSTRING). + In production include the `--secure` option which redirects all requests to HTTPS. Note that PostgREST does not handle the SSL internally and must be put behind another server that does (such From 2cbf2af6c79769c3276161275c51b848c2d06efa Mon Sep 17 00:00:00 2001 From: calebmer Date: Sun, 11 Oct 2015 16:51:52 -0400 Subject: [PATCH 6/9] Fix readme bash syntax --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dd8d92c10..121c4f38d 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,9 @@ your own projects. Download the binary ([latest release](https://github.com/begriffs/postgrest/releases/latest)) and invoke like so: ```bash -postgrest postgres://postgres:foobar@localhost:5432/my_db - --port 3000 \ - --schema public \ +postgrest postgres://postgres:foobar@localhost:5432/my_db \ + --port 3000 \ + --schema public \ --anonymous postgres \ --pool 200 ``` From e37b2d8c5947d38cecbd6c99bfcc8ba8cdd9b327 Mon Sep 17 00:00:00 2001 From: Joe Nelson Date: Mon, 12 Oct 2015 16:58:15 -0700 Subject: [PATCH 7/9] Provide detailed logging for any db errors caused internally by postgrest --- postgrest.cabal | 1 + src/PostgREST/Main.hs | 38 +++++++++++++++++++++----------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/postgrest.cabal b/postgrest.cabal index af08f499e..55d5fcc68 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -37,6 +37,7 @@ executable postgrest , case-insensitive , scientific, time , aeson >= 0.8, network >= 2.6 + , aeson-pretty >= 0.7 && < 0.8 , bytestring, text, split, string-conversions , stringsearch , containers, unordered-containers diff --git a/src/PostgREST/Main.hs b/src/PostgREST/Main.hs index 180bde91a..565eef737 100644 --- a/src/PostgREST/Main.hs +++ b/src/PostgREST/Main.hs @@ -1,39 +1,41 @@ module Main where +import PostgREST.App +import PostgREST.Config (AppConfig (..), + minimumPgVersion, + prettyVersion, + readOptions) +import PostgREST.Error (errResponse, PgError) +import PostgREST.Middleware import PostgREST.PgStructure import PostgREST.Types -import Network.Wai - -import PostgREST.App -import PostgREST.Error (errResponse) -import PostgREST.Middleware import Control.Monad (unless) import Control.Monad.IO.Class (liftIO) +import Data.Aeson.Encode.Pretty (encodePretty) import Data.Functor.Identity import Data.Monoid ((<>)) import Data.String.Conversions (cs) import Data.Text (Text) import qualified Hasql as H import qualified Hasql.Postgres as P +import Network.Wai import Network.Wai.Handler.Warp hiding (Connection) import Network.Wai.Middleware.RequestLogger (logStdout) - import System.IO (BufferMode (..), hSetBuffering, stderr, stdin, stdout) -import PostgREST.Config (AppConfig (..), - prettyVersion, - readOptions, - minimumPgVersion) isServerVersionSupported :: H.Session P.Postgres IO Bool isServerVersionSupported = do Identity (row :: Text) <- H.tx Nothing $ H.singleEx [H.stmt|SHOW server_version_num|] return $ read (cs row) >= minimumPgVersion +hasqlError :: PgError -> IO a +hasqlError = error . cs . encodePretty + main :: IO () main = do hSetBuffering stdout LineBuffering @@ -61,15 +63,17 @@ main = do pool :: H.Pool P.Postgres <- H.acquirePool pgSettings poolSettings supportedOrError <- H.session pool isServerVersionSupported - either (fail . show) + either hasqlError (\supported -> unless supported $ - fail "Cannot run in this PostgreSQL version, PostgREST needs at least 9.2.0" + error "Cannot run in this PostgreSQL version, PostgREST needs at least 9.2.0" ) supportedOrError - Right authenticator <- H.session pool $ do - Identity (role :: Text) <- H.tx Nothing $ H.singleEx [H.stmt|SELECT SESSION_USER|] + roleOrError <- H.session pool $ do + Identity (role :: Text) <- H.tx Nothing $ H.singleEx + [H.stmt|SELECT SESSION_USER|] return role + authenticator <- either hasqlError return roleOrError let txSettings = Just (H.ReadCommitted, Just True) metadata <- H.session pool $ H.tx txSettings $ do @@ -79,15 +83,15 @@ main = do keys <- allPrimaryKeys return (tabs, rels, cols, keys) - dbstructure <- case metadata of - Left e -> fail $ show e - Right (tabs, rels, cols, keys) -> + dbstructure <- either hasqlError + (\(tabs, rels, cols, keys) -> return DbStructure { tables=tabs , columns=cols , relations=rels , primaryKeys=keys } + ) metadata runSettings appSettings $ middle $ \ req respond -> do body <- strictRequestBody req From 3733a84a38e7d8866a954a2c3b0369a155400a1d Mon Sep 17 00:00:00 2001 From: Joe Nelson Date: Mon, 12 Oct 2015 17:02:33 -0700 Subject: [PATCH 8/9] Use type alias for clarity --- src/PostgREST/App.hs | 2 +- src/PostgREST/Middleware.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index 916098229..40c86babd 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -57,7 +57,7 @@ import PostgREST.Types import Prelude -app :: DbStructure -> AppConfig -> Text -> BL.ByteString -> DbRole -> Request -> H.Tx P.Postgres s Response +app :: DbStructure -> AppConfig -> DbRole -> BL.ByteString -> DbRole -> Request -> H.Tx P.Postgres s Response app dbstructure conf authenticator reqBody dbrole req = case (path, verb) of diff --git a/src/PostgREST/Middleware.hs b/src/PostgREST/Middleware.hs index e841c592c..9dd3a5c48 100644 --- a/src/PostgREST/Middleware.hs +++ b/src/PostgREST/Middleware.hs @@ -33,7 +33,7 @@ import PostgREST.Config (AppConfig (..), corsPolicy) import Prelude -authenticated :: forall s. AppConfig -> Text -> +authenticated :: forall s. AppConfig -> DbRole -> (DbRole -> Request -> H.Tx P.Postgres s Response) -> Request -> H.Tx P.Postgres s Response authenticated conf authenticator app req = do From 6dbb69a8283d0843eeb7a0b16adac4576049da92 Mon Sep 17 00:00:00 2001 From: Joe Nelson Date: Mon, 12 Oct 2015 17:03:02 -0700 Subject: [PATCH 9/9] Ensure that version error message tracks changing requirements --- src/PostgREST/Main.hs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PostgREST/Main.hs b/src/PostgREST/Main.hs index 565eef737..86e710424 100644 --- a/src/PostgREST/Main.hs +++ b/src/PostgREST/Main.hs @@ -66,7 +66,9 @@ main = do either hasqlError (\supported -> unless supported $ - error "Cannot run in this PostgreSQL version, PostgREST needs at least 9.2.0" + error ( + "Cannot run in this PostgreSQL version, PostgREST needs at least " + <> show minimumPgVersion) ) supportedOrError roleOrError <- H.session pool $ do