v0.15.0 (Beta 18.10.2021)
A new IHP release with new features and many bug fixes. This release also includes the Stripe Integration and Docker support for IHP Pro and IHP Business users 🚀
IHP is a modern batteries-included haskell web framework, built on top of Haskell and Nix. Blazing fast, secure, easy to refactor and the best developer experience with everything you need - from prototype to production.
Major Changes
-
💰 Payments with Stripe:
This version finally ships one of the most requested features, the Stripe Integration. With the new Stripe Integration you can easily deal with payments and subscriptions in your projects. If you ever wanted to build as SaaS with Haskell, today is the best day to start! 💻module Web.Controller.CheckoutSessions where import Web.Controller.Prelude import qualified IHP.Stripe.Types as Stripe import qualified IHP.Stripe.Actions as Stripe instance Controller CheckoutSessionsController where beforeAction = ensureIsUser action CreateCheckoutSessionAction = do plan <- query @Plan |> fetchOne stripeCheckoutSession <- Stripe.send Stripe.CreateCheckoutSession { successUrl = urlTo CheckoutSuccessAction , cancelUrl = urlTo CheckoutCancelAction , mode = "subscription" , paymentMethodTypes = ["card"] , customer = get #stripeCustomerId currentUser , lineItem = Stripe.LineItem { price = get #stripePriceId plan , quantity = 1 , taxRate = Nothing , adjustableQuantity = Nothing } , metadata = [ ("userId", tshow currentUserId) , ("planId", tshow planId) ] } redirectToUrl (get #url stripeCheckoutSession) action CheckoutSuccessAction = do plan <- fetchOne (get #planId currentUser) setSuccessMessage ("You're on the " <> get #name plan <> " plan now!") redirectTo SwitchToProAction action CheckoutCancelAction = redirectTo PricingAction
-
📦 Docker Support:
Thanks to the new docker integration it's now easier than ever to ship your IHP apps into production!$ ihp-app-to-docker-image ... ✅ The docker image is at 'docker.tar.gz'
-
🕸️ SEO Improvements:
You can now dynamically manage meta tags like<meta property="og:description" content="dynamic content"/>
in your IHP apps.instance View MyView where beforeRender MyView { post } = do setOGTitle (get #title post) setOGDescription (get #summary post) setOGUrl (urlTo ShowPostAction { .. }) case get #imageUrl post of Just url -> setOGImage url Nothing -> pure () -- When setOGImage is not called, the og:image tag will not be rendered -- ...
-
🌐 HTML in Validation Error Messages:
You can now useattachFailureHtml
if you want to write a custom validation logic that uses HTML in it's error message:post |> attachFailureHtml #title [hsx|Invalid value. <a href="https://example.com/docs">Check the documentation</a>|] -- Link will work as expected, as it's HSX
Learn more about HTML validation errors in the documentation
-
🔍 Documentation Search:
There's now a new Agolia-based Search in the IHP Documentation
Search for Something
-
🎨 New Design for the API Reference:
Our haddock-based API reference now has a custom CSS that gives it a stunning new look!
Check out the API Reference
-
💽 Auto-generated Migrations:
The Schema Designer now keeps track of your changes. Whenever you generate a new migration from the Web-based Code Generator or thenew-migration
CLI command, it will prefill the.sql
file with the steps needed to migrate. -
🧰 Tooling Updates:
We've updated several packages available in your IHP development environment:GHC: 8.10.5 -> 8.10.7 Haskell Language Server: 1.1.0.0 -> 1.4.0.0
-
🧪 Testing Improvements:
Test modules now run in their own separate database. On test start IHP will create a new database and import theApplication/Schema.sql
. After test completion the database is deleted.In previous IHP versions tests used the development database by default.
Here's an example of how controllers tests can now look like:
module Test.Controller.PostsSpec where import Network.HTTP.Types.Status import IHP.Prelude import IHP.QueryBuilder (query) import IHP.Test.Mocking import IHP.Fetch import IHP.FrameworkConfig import IHP.HaskellSupport import Test.Hspec import Config import Generated.Types import Web.Routes import Web.Types import Web.Controller.Posts () import Web.FrontController () import Network.Wai import IHP.ControllerPrelude tests :: Spec tests = aroundAll (withIHPApp WebApplication config) do describe "PostsController" $ do it "has no existing posts" $ withContext do count <- query @Post |> fetchCount count `shouldBe` 0 it "calling NewPostAction will render a new form" $ withContext do mockActionStatus NewPostAction `shouldReturn` status200 it "creates a new post" $ withParams [("title", "Post title"), ("body", "Body of post")] do response <- callAction CreatePostAction let (Just location) = (lookup "Location" (responseHeaders response)) location `shouldBe` "http://localhost:8000/Posts" -- Only one post should exist. count <- query @Post |> fetchCount count `shouldBe` 1 -- Fetch the new post. post <- query @Post |> fetchOne get #title post `shouldBe` "Post title" get #body post `shouldBe` "Body of post" it "can show posts" $ withContext do post <- newRecord @Post |> set #title "Lorem Ipsum" |> set #body "**Mark down**" |> createRecord response <- callAction ShowPostAction { postId = get #id post } response `responseStatusShouldBe` status200 response `responseBodyShouldContain` "Lorem Ipsum" -- For debugging purposes you could do the following, to -- see the HTML printed out on the terminal. body <- responseBody response putStrLn (cs body)
Inside the test you can use
withUser
to call an action as a logged in user:-- Create a user for our test case user <- newRecord @User |> set #email "marc@digitallyinduced.com" |> createRecord -- Log into the user and then call CreatePostAction response <- withUser user do callAction CreatePostAction
-
🪄 Experimental: New DataSync API
Building Hybrid & Single-Page Apps with Realtime Functionality is about to get much easier with IHP.
This release includes an early version of the new DataSync API that allows you to query your database from within JS code on the frontend:class TodoList extends React.Component { constructor(props) { super(props); this.state = { tasks: null }; } async componentDidMount() { initIHPBackend({ host: 'https://ojomabrabrdiuzxydbgbebztjlejwcey.ihpapp.com' }) await ensureIsUser(); await query('tasks') .orderBy('createdAt') .fetchAndRefresh(tasks => this.setState({ tasks })) } render() { const { tasks } = this.state; if (tasks === null) { return <div className="spinner-border text-primary" role="status"> <span className="sr-only">Loading...</span> </div>; } return <div> <AppNavbar/> {tasks.map(task => <TaskItem task={task} key={task.id}/>)} <NewTodo/> </div> } }
In the react component above you can see that our JS is able to use IHP's query builder using
query(..).fetch()
, just like you would do it in your haskell code. You can also update and delete records:function TaskItem({ task }) { const taskIdAttr = "task-" + task.id; return <div className="form-group form-check"> <input id={taskIdAttr} type="checkbox" checked={task.isCompleted} onChange={() => updateRecordById('tasks', task.id, { isCompleted: !task.isCompleted })} className="mr-2" /> <label className="form-check-label" htmlFor={taskIdAttr}>{task.title}</label> <button className="btn btn-link text-danger" onClick={() => deleteRecordById('tasks', task.id) }>Delete</button> </div> }
Authentication is handled using Postgres Row-level-Security and Policies (defined in the
Schema.sql
) like this:CREATE POLICY "Users can manage their tasks" ON tasks USING (user_id = ihp_user_id()) WITH CHECK (user_id = ihp_user_id());
You can find a demo app using this API here:
https://wlulygxcdknebrolshgolktblgapwnxn.ihpapp.com/ (Login:demo@digitallyinduced.com
Password:demo
).
The app is real-time, try to open it in two browsers and change something :)
Other Changes
- Added note on GitPod to install docs
- Export Control.Monad.Fail.fail from IHP.Prelude
- Use IHP logger instead of directly printing to output in the dev server
- Fix bug regarding order of fields in data editor
- Replaced setProcessLimits with custom ulimits in start script: The setProcessLimits was not working as expected
- Reduce memory usage by IHP dev server
- Add an example of the BlazeHTML equivalent of HSX
- potential mod for create form actions to helpers.js so that it is the same with and without Turbolinks on Left branch from failed form submission.
- Modified codegen to include app.js in ihp boilerplate static
- remove some unneeded/wrong rounded corners
- add matchesRegex validator
- add isSlug validator
- add missing INLINABLEs
- Fixed queryOr causing strange type errors in complex Queries.
- Fixed generated jobs not having run_at column
- Improved error handling when database state is messed up
- Simplified layout of pagination to get rid of the horizontal scrollbar.
- Added documentation for running IHP dev tooling on hosts other than localhost
- Fixed live reloading not working if the dev server is not running on localhost
- Support https urls in live reloading
- Fixed hardcoded localhost url in devtool navigation
- Reference new gitpod ihp template in the docs
- Fixed outdated documentation on the request context
- Added documentation on how to customize the page title
- Added documentation on layout variables
- Added documentation on how to deal with multi records in a single form
- add setMaybe which ignores updates on Nothing
- The
Config/client_session_key.aes
can now be set via theIHP_SESSION_SECRET
env var - Improved cross compiling support of IHP (needed for the docker integration)
- Added missing attributes to HSX: frameborder, allow, allowfullscreen
- Added example projects section
- Update OAuth documentation
- Fixed fill not able to deal with certain JSON bodies
- Added a onlyAllowMethods function to filter for specific methods when building custom routing parsers
- Re-export some more IHP apis
- Added new validationResult and validationResultMaybe form helpers to render validation errors with custom form fields
- Added Table record instance: This new instance will be used for further simplifications of the database APIs
- Use explicit list of columns in SELECT queries instead of using
SELECT *
: This avoids issues when the database schema is being migrated - Show database outdated error screen if column is missing exception is thrown
- Added notes on how to order with collectionFetchRelated
- Replaced nub with nubOrd/nubInt which are more efficient
- Fixed redirectTo not working in controllers where no view is imported
- Don't use scientific notation in input fields with float / double values
- Added documentation on how to render a many to many index view
- Support ENABLE ROW LEVEL SECURITY statements in Schema.sql
- Changed submit button default text from 'Create CamelCase' to 'Create Camel Case'
- Improved docs for fetchRelated
- Parse a larger set of CREATE FUNCTION statements
- added support for CREATE POLICY statements
- Add note about multiple nested resources
- Added renderJsonWithStatusCode
- Fixed livereloading.js still using :8002, even though it's :8001 now
- All function names in the Documentation now link to the API Reference
- Fixed parenthese not correctly applied in SQL Compiler.
- Added help icon in dev tools
- create filterWherePast and filterWhereFuture functions
- Fixed makefile sometimes returning a successful exit code even though compilation failed
- Add example how to link to Jobs
- Expand docs on Raw SQL Queries
- Cleaned up redundant constraints and unused imports
- Attempt to fix Testing docs
- Add local with MailHog instructions
- Added cache busting to the IHP Dev server
- Show IHP version in help menu
- Added upgrade to IHP Pro button
- Added more examples: chat and blog
- Add missing case for Job renderStatus
- Added notes on hoogle
- Add paramListOrNothing function
- Do not read global packages in
run-script
- Fixed Data Editor turning NULL values into empty strings when editing a row
- Improve error message when JSON view is not implemented
- Evaluate initial auto refresh response to HNF to fix unexpected behaviour when there's an error in the response
- Added support for CREATE SEQUENCE statements
- Added maxConcurrency option for throttling job worker concurrency
- Fixed Nothing in test output
- Use ByteString's in the Session API instead of inefficent haskell String's
- Simplified duplicate session vault handling logic
- Removed session helper functions: Most of the removed functions haven't been used in real world applications. So let's get rid of them.
- Configure asset version using FrameworkConfig: The previous approach evaluated two env vars on every web request which is not efficient
Pull Requests
- Specify Web/Types.hs by @dharmatech in #1058
- Export Control.Monad.Fail.fail from IHP.Prelude by @mpscholten in #1059
- Add note regarding the
fromLabel @"companyId"
bug by @dharmatech in #1060 - Fix bug regarding order of fields in data editor by @joshua-obritsch in #1061
- Import cleanup by @mpscholten in #1062
- Link to BlazeHtml by @dharmatech in #1063
- Fix typo by @dharmatech in #1065
- Add an example of the BlazeHTML equivalent of HSX by @dharmatech in #1064
- potential mod for create form actions to helpers.js by @Montmorency in #1066
- Fix typo (it's -> its) by @dharmatech in #1067
- fix a couple of it's -> its by @hendi in #1072
- remove some unneeded/wrong rounded corners by @hendi in #1073
- add
matchesRegex
validator by @hendi in #1074 - Slug validator by @hendi in #1076
- Typo on ContentType in CanSelect form docs. by @Montmorency in #1078
- Fixed queryOr causing strange type errors in complex Queries. by @mpscholten in #1081
- Codegen app.js by @Montmorency in #1079
- Simplified layout of pagination to get rid of the horizontal scrollba… by @mpscholten in #1082
- add setIfJust which ignores updates on Nothing by @hendi in #1088
- Update OAuth documentation by @GarrisonJ in #1090
- Added support for using HTML inside validation error messages by @mpscholten in #1094
- Add note about multiple nested resources by @amitaibu in #1101
- More multiple Include docs by @amitaibu in #1102
- Add missing
src
on<script>
tag in docs by @amitaibu in #1105 - fix upgrade.md for 0.13.0 -> 0.13.1 by @CSchank in #1106
- link api docs in guide by @jeyj0 in #1104
- DataSync by @mpscholten in #1103
- create filterWherePast and filterWhereFuture functions by @jeyj0 in #1107
- Fix typo in make file by @amitaibu in #1111
- Add example how to link to Jobs by @amitaibu in #1119
- Fix typo: paramaters->parameters by @stianlagstad in #1118
- Fix docs about Retrieving All Error Messages by @amitaibu in #1117
- Expand docs on Raw SQL Queries by @amitaibu in #1124
- Add missing case for Job renderStatus by @amitaibu in #1131
- Add local mail testing with MailHog instructions by @amitaibu in #1127
- Doc for Type errors in
build/Generated/Types.hs
by @amitaibu in #1132 - Add paramListOrNothing function by @amitaibu in #1130
- Fix wrong dir in docs by @amitaibu in #1133
- run-script Do not read global packages by @s0kil in #1135
- Improve error message when JSON view is not implemented by @amitaibu in #1139
- Updated to a recent nixpkgs version and updated ghc to 8.10.7 by @mpscholten in #1138
- Added auto-migration based on operation tracking approach by @mpscholten in #1136
- Refactored request/response mocking to use runActionWithNewContext. by @mpscholten in #1141
- Added maxConcurrency option for throttling job worker concurrency by @mpscholten in #1144
- Add docs about Jobs concurrency by @amitaibu in #1148
- Improved testing functionality by @mpscholten in #1146
- Session refactoring by @mpscholten in #1149
- Fix Testing docs by @amitaibu in #1126
- Upgrade tests docs by @amitaibu in #1150
New Contributors
Full Changelog: v0.14.0...v0.15.0
Feature Voting
Help decide what's coming next to IHP by using the Feature Voting!
Updating
See the UPGRADE.md for upgrade instructions.
If you have any problems with updating, let us know on the IHP forum.
📧 To stay in the loop, subscribe to the IHP release emails. Or follow digitally induced on twitter.