Releases: digitallyinduced/ihp
v0.17.0 (Beta 14.12.2021)
A new IHP release with new features and many bug fixes 🚀
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
-
💽 Improved Migration Workflow:
Since v0.16.0 IHP is already automatically generating a migration prefilled with the recommended changes that need to be applied to your database. Previously IHP was tracking all changes you've made in the Schema Designer and re-applied them to the migration sql file.With this release the approach has been changed to a database diffing approach: IHP will now diff the Schema.sql against your local database and generate a efficient patch to bring both in sync again.
This also affects the local dev tools. The
Update DB
button has been removed entirely. When there are unmigrated changes locally, the Schema Designer will suggest you to generate and run a migration, like this: -
👨💻 Optional HSX Attributes:
HSX gained special handling forMaybe
values to make it easy to deal with optional attributes.You can write
let target :: Maybe Text target = Just "_blank" in [hsx|<a target={target} />|]
and it will render to:
<a target="blank" />
Using
Nothing
results in thetarget
attribute not being in the output HTML:let target :: Maybe Text target = Nothing in [hsx|<a target={target} />|]
This will render to:
<a />
-
DataSync Updates:
IHP DataSync now uses React Functional Components instead of Class based approach used in previous versions::function TodoList() { const todos = useQuery(query('todos')); if (todos === null) { return <div className="spinner-border text-primary" role="status"> <span className="sr-only">Loading...</span> </div>; } return <div> {todos.map(todo => <div>{todo.title}</div>)} </div> }
Additionally this release contains many bug fixes for DataSync and large performance and usability improvements.
-
🧰 Haskell Language Server: Performance Improvements
We've updated the Haskell Language Server from 1.4.0.0 to the latest version 1.5.1.0.With HLS 1.5.1.0 the performance should be much better. If you're curious, here's a nice presentation of what has changed in HLS.
If you still have performance issues, please submit issues directly here haskell/haskell-language-server#2340
-
📫 Encrypted SMTP
When configuring the IHP Mailer to use SMTP, you can now specify the encryption by setting theencryption
field:import IHP.Mail config :: ConfigBuilder config = do option $ SMTP { host = "smtp.myisp.com" , port = 2525 , credentials = Nothing , encryption = STARTTLS -- <-- NEW }
Other Changes
- extended sql parser to be able to reverse engineer the local db
- support encrypted SMTP (TLS or STARTTLS)
- Update TW docs to use JIT mode
- Set HOME env var during build to fix npm issues
- Fixed background workers not running when the local dev db was initlaized after the app is already started
- Fixed DynamicQueryCompiler adding too many vars in the empty case
- Fixed form input with name="action" or name="method" breaking submission. Fixes #1203
- Expand example on adding custom attributes
- Show how we pipe attributes
- Hide Pagination if only a single page
- Added VSCode + HLS troubleshooting section
- Fixed dev server thinking postgres is started while it's still in the startup phase. Fixes #1201
- Fixed postgres start event not correctly detected
- Use bytestring builder for efficiently building postgres logs
- Completed docs for DataSync
- Fixed ihp_authenticated role sometimes not having permission to access the public schema
- Delay app start until postgres is ready. Fixes #1210
- Recipes: rich text editor, highlighting :target
- change wrong message after a Job got set to retry
- fix "Run again" button on Job details view
- Added MINIMUM_REVISION env var to allow skipping old migrations
- Add troubleshooting steps for Purescript on IHP Cloud
- Support null values in DynamicQuery
- Added auto-retry to datasync connection
- Improved error handling of DataSync crud functions
- Added useQueryResult effect hook
- Fixed form submission fails for GET forms where the actions is only a relative path
- Improved error messages shown when a data sync request fails
- Fixed datasync not correctly dealing with integer values
- Validate input parameters to initIHPBackend as this is a common source of errors
- DataSubscription constructor now validates that the passed query object looks like a query object
- Add cabal update-hint in section about cabal2nix
- Fixed DataSync getting out of sync because Update events are not sent sometimes
- Added windofchange to IHP examples
- Fixed inconsistent JS spelling
- Modernize FormField's CSSframework integration
- Use native js modules for exposing DataSync functionality
- Moved DataSync js files to their own npm package
- Fixed parser not able to parse pg_dump output
- Added support for optional HSX attributes
- Fixed turbolinks not used when using the side navigation in the devtools
- [Prevent changing URL on form with error](fb89a477b6a...
v0.16.0 (Beta 06.11.2021)
A new IHP release with new features and many bug fixes 🚀
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
-
🍞 Breadcrumbs:
@amitaibu added a new API for defining breadcrumb navigations inside your views. This new API makes it easier to customize the breadcrumb rendering across your app, e.g. to use a tailwind css based breadcrumb instead of the default bootstrap based one.instance View ShowView where html ShowView { .. } = [hsx| {breadcrumb} <h1>Show Post</h1> <p>{post}</p> |] where breadcrumb = renderBreadcrumb [ breadcrumbLink "Posts" PostsAction , breadcrumbText "Show Post" ]
The new breadcrumb functions are automatically used when you generate new controllers or views using the code generators.
Check out the docs: https://ihp.digitallyinduced.com/Guide/view.html#breadcrumbs
-
📄 Tailwind + Pagination:
The CSS classes and HTML code of IHP's pagination can be customized now. This makes it possible to now use the pagination with tailwindcss. -
🏭 Build Improvements:
You can now runnix-build
inside your IHP project and it will build the whole project, including background workers and IHP scripts:# Optional, if you skip this the binary will not be optimized by GHC make prepare-optimized-nix-build # The actual build process nix-build
This will build a nix package that contains the following binaries:
RunProdServer
, the binary to start web serverRunJobs
, if you’re using the IHP job queue, this binary will be the entrypoint for the workers a binary for each script inApplication/Script
, e.g.Welcome
forApplication/Script/Welcome.hs
- The build contains an automatic hash for the
IHP_ASSET_VERSION
env variable, so cache busting should work out of the box.
This will make it significant easier to deploy IHP apps.
If you wondered: These build improvements are also what powers the new Experimental Deployment Process in IHP Cloud. -
💻 Helpers to Load Env Vars:
Along with the recent deployment changes, we're also making it easier to define custom config parameters based on environment variables to your app.Inside Config.hs you can now use the
env
andenvOrNothing
functions to read env variables:module Config where config :: ConfigBuilder config = do maxRetryCount <- env @Int "MAX_RETRY_COUNT" appName <- env @Text "APP_NAME" redisUrl :: Maybe Text <- envOrNothing "REDIS_URL"
-
SchemaDesigner: Support for Postgres Policies
You can now create and manage Postgres Policies inside the Schema Designer:This is still an early version and we'll extend the policy editor in future IHP versions. It's going to be used together with the new DataSync APIs soon.
-
🎨 New 404 Page
We designed a new nice looking standard 404 for IHP apps :)Check out the docs if you want to have your own custom 404 page.
Other Changes
- Add warning for direnv hook to ihp-new
- Add automatic hook installation when first installing direnv
- Fixed handling of whitespace in sql line comments
- Add "Connecting to DB via UI" docs
- Fixed tests not import IHPSchema.sql and thus failing when built-in data types are used. Fixes #1155
- Added failing test for enum is defined after use #1154
- Fixed enum sometimes only defined after usage in CREATE TABLE statements. Fixes #1154
- Support adding values to a enum in the auto-migration
- Use bracket for allocating test database
- customNotFoundResponse in Guide/recipes.markdown gets a better typeannotation - known to work for versions from ihp 0.11 up to 0.15
- Limit page size of pagination
- add imports to IHP/Guide/recipes.markdown - improves reproducibility
- Better handleof page limits in Pagination
- Move Pagination to CSS framework
- Make ihp env configurable via IHP_ENV env var
- Added a callActionWithParams
- Removed withParams, use callActionWithParams instead
- Added idToParam
- Add callJob to testing
- Made api reference header bar sticky
- Clarified forever in websocket docs
- renderNotFound uses the same approach as normal 404 errors in IHP now. Fixes #1180
- Build docs with github actions and push to s3
- Added github action to build ihp-new package
- Fixed live reloading sometimes broken after running a job in the background worker
- Fixed RLS user not having permissions to tables after it's created by IHP
- Fixed padding of table in schema designer not consistent with padding of titles
- First draft of the data sync docs
- Updated Flatpickr to v4.6.9: Fixed a browser issue that we've found in a client app at di
- Fixed 'make postgres' not working
- Extend nodeOuterHtml to all node types
- Added sendJSON to the public IHP api. Fixes #1181
- Run job queue's createNotificationTrigger inside a single transaction: This fixes jobs sometimes not being picked up directly
- Wrap AutoRefresh's trigger setup in a transaction to avoid events getting lost
- Fixed retry functionality in the job dashboard not working. Fixes #1167
- Fixed IHPSchema.sql not imported when the IHP database is created on first use in dev mode
New Contributors
- @tcmal made their first contribution in #1153
- @neongreen made their first contribution in #1193
Full Changelog: v0.15.0...v0.16.0
Feature Voting
[Help decide what's coming next to IHP by using the Feature Voting!](https://ihp.digitallyinduce...
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 "[email protected]" |> 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> <AppNav...
v0.14.0 (Beta 27.08.2021)
A new IHP release with new features and many bug fixes. This release is also the first release that comes with an IHP Pro release 🚀
Major Changes
-
💎 IHP Pro Available now:
The new IHP Pro and IHP Business subscriptions are available now 🎉Check them out here!
If you're curious about the details, check out this forum thread! -
Allow scheduling jobs at a specific time with
runAt
:
The job queue table's now have arun_at
column. Jobs are only executed after the specified time.
The job runner is now also polling the job queue at regular intervals as otherwise scheduled jobs will not be picked up when there's no activity on the job queue table.Here's an example of a job scheduled to run in ten minutes from now:
inTenMinutes <- addUTCTime (10 * 60) <$> getCurrentTime newRecord @DeleteContainerJob |> set #containerId (get #containerId containerBuild) |> set #runAt inTenMinutes |> createRecord
-
New IHP Logo:
The dev server now displays our new beautiful IHP logo 💘
-
Customize File Upload Limits:
By default IHP has a max file size limit when uploading files to the IHP server. You can now customize these limits inside yourConfig/Config.hs
. -
📫 Email Confirmation:
One of the first IHP Pro features now made it :) There's now a built-in standard way to deal with email confirmation. -
OAuth:
Login with Google and Google are now finally available in IHP.This new feature is also part of IHP Pro. Once you're app is running IHP Pro, follow this Guide on how to set it up.
-
New Form Helper:
fileField
:
Previously you had to manually type the HTML code for<input type="file"/>
fields in your forms. We've finally added the missing helper 🎉renderForm :: Company -> Html renderForm company = formFor company [hsx| {(textField #name)} {(fileField #logoUrl)} {submitButton} |]
-
New Function:
createTemporaryDownloadUrlFromPathWithExpiredAt
: Get signed S3 download urls with a custom expiration time
This function is similiar tocreateTemporaryDownloadUrlFromPath
, but you can pass a custom expiration time. ThecreateTemporaryDownloadUrlFromPath
function always generates urls that are valid for 7 days.E.g. this example generates a presigned S3 url to a logo that expires in 5 minutes:
let validInSeconds = 5 * 60 signedUrl <- createTemporaryDownloadUrlFromPathWithExpiredAt validInSeconds "logos/8ed22caa-11ea-4c45-a05e-91a51e72558d" let url :: Text = get #url signedUrl let expiredAt :: UTCTime = get #expiredAt signedUrl
-
Improved Support for GitPod & GitHub Codespaces:
Previously IHP's dev tooling was only running when it was called atlocalhost:8000
. This is a problem in services like GitPod or GitHub spaces, where the the host might not belocalhost
.There are now two env variables
IHP_BASEURL
andIHP_IDE_BASEURL
to override theBaseUrl
configured inConfig/Config.hs
at runtime:export IHP_BASEURL=http://dev.myhost.lo:1337 export IHP_IDE_BASEURL=http://dev.myhost.lo:1338 ./start
We've already integrated this into the IHP GitPod template. If you're curious, follow this link to start a GitPod development session (it's free) with the IHP Template.
These new env vars might also be useful if you run your IHP dev tooling on a different device than your editor inside your local network.
Other Changes
- Fully evaluate blaze html expression before returning the html response in dev mode. This improves error messages when there's an
error
call inside a view. - Fixed many-to-many relations with Int PKs
- refuse to build union query if one of the queries contains joins
- Fixed deleteRecord for composite PKs. Fixed several bugs in code generation for composite PKs
- Fixed issues with loading IHP tests into ghci
- Pagination functions now also work with queries that have a JOIN
- Simplified constraints required for calling isNew: We can detect if a record is new by checking the originalDatabaseRecord field of the meta field. This allows us to drop the constraints that require the form record to have a id field. This allows to use formFor with records that don't have an id field.
- Keep message through multiple redirects
- Fixed crash in SchemaCompiler if the enum has no values.
- Added Int type to SQL Parser: Previously all Int's have been handled like variables (in a way that makes sense :D 1 is just a symbol standing for number 1). This has caused issues as we introduced support for Double literal expressions recently. Now integer literals have their own representation in the AST.
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.
v0.13.1
A small out-of-line release that fixes two issues introduced in v0.13.0
.
Specifically it fixes:
- a crash in the Schema Designer when adding columns with foreign key constraints
- the dev server not starting in cases where multiple
putStrLn
are in scope
v0.13.0 (Beta 06.08.2021)
A new IHP release with new features and bug fixes. Over 120 commits have been merged since the last release 🚀
Major Changes
- New Function:
storeFileFromUrl
Quickly upload something from an existing URL into your S3 bucket:Learn more in the documentation.let externalUrl = "http://example/picture.jpg" let options :: StoreFileOptions = def { directory = "pictures" } storedFile <- storeFileFromUrl externalUrl options let newUrl = get #url storedFile
- New Function:
storeFileFromPath
Quickly upload something from the current app server into your S3 bucket:Learn more in the documentation.let options :: StoreFileOptions = def { directory = "pictures" } storedFile <- storeFileFromPath "picture.jpg" options let newUrl = get #url storedFile
- New Storage Backend: Minio
You can now store files on a minio server if you don't want to use S3.
Learn more on how to set up minio in the documentation - New Error Messages when an Action is called with the wrong HTTP Method
-
Send Email Attachments with IHP Mail
This version adds support to have mail attachments when sending mails with your IHP app.module Web.Mail.Users.Confirmation where import Web.View.Prelude import IHP.MailPrelude data ConfirmationMail = ConfirmationMail { user :: User } instance BuildMail ConfirmationMail where subject = "Subject" to ConfirmationMail { .. } = Address { addressName = Just "F L", addressEmail = "[email protected]" } from = "[email protected]" html ConfirmationMail { .. } = [hsx| Hello World |] attachments ConfirmationMail { .. } = [ MailAttachment { name = "attachment.xml", contentType = "application/xml", content = "<xml>...</xml>" } ]
-
New View Helper:
time
Similiar totimeAgo
, but always showing an absolute time.
Learn more. -
Package Updates
We're updating our nixpkgs version to update several dependencies of IHP:
- Haskell Language Server is updated from v0.7 to v1.2
- GHC is updated from v8.10.3 to v8.10.4
Other Changes
- Export validateIsUniqueCaseInsensitive function
- Redesigned Session interface: You can now store any data type inside the session instead of previously being limited to Text only
- Significant compile-time improvements for large IHP projects: We fixed some low hanging fruits in our build approach to significantly improve compile times for projects that have scripts and background workers
- Fixed that
nix-build
fails by default in IHP projects - Fixed maxItems query value in pagination sometimes not a valid numeric value
- Added flag to controll scroll to top behaviour
- The collectionFetchRelated family of functions now has a new missing part:
collectionFetchRelatedOrNothing
- Fixed deleting a column when the table has an index fails.
- Fixed modules not being a restricted keyword
- Improved documentation on database naming conventions
- Export uploadToStorage
- Fixed compile-time error screen of the dev server sometimes not having the expected styled due to an issue in the live reloading
- Allow Setting disabled Attribute On Form Fields
- Allow Setting response headers
- Add support for DOUBLE/Float default values in sql and support type casts in sql default value expressions
- Minor cosmetic improvement in the index view of the schema designer
- Import Application.Helper.View inside the Layout.hs by default
- Enable TemplateHaskell extension by default when compiling for production
- Added filesByName to access multiple uploaded files
- Added assetPath and pageTitle to default Layout.hs
- Fix NUMERIC support (map to Data.Scientific)
- Fixed exception during app start not shown in the dev server
- Added note on using snake case for enum values
- Added "Save & Add Another" button to enum value form
- Close modals in schema designer on esc key press
- link slack community in generated welcome view
- Fixed AutoRoute accepting anything that looks like an UUID as a UUID, even when the action doesn't expect an UUID
- Improved error messages when a mandatory AutoRoute request parameter is missing
- Warn if "Update DB" is used when the schema designer code editor has unsaved changes
- Made param error messages more human friendly
- Support where clauses in CREATE INDEX statements
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.
v0.12.0 (Beta 10.07.2021)
A new IHP release with new features and bug fixes. Over 100 commits have been merged since the last release 🚀
Major Changes
-
Pagination:
IHP has a new built-in pagination module.Given an action like this:
action PostsAction = do posts <- query @Post |> orderBy #createdAt |> fetch render IndexView { .. }
We can paginate our results by adding a call to
paginate
:action PostsAction = do (postsQuery, pagination) <- query @Post |> orderBy #createdAt |> paginate posts <- postsQuery |> fetch render IndexView { .. }
We also need to change the view:
module Web.View.Posts.Index where import Web.View.Prelude data IndexView = IndexView { posts :: [Post] , pagination :: Pagination -- <---- Pass pagination variable to the view } instance View IndexView where html IndexView { .. } = [hsx| <div> {forEach posts renderPost} </div> {renderPagination pagination} -- <---- CALL renderPagination |]
Here's how the pagination looks like in the view:
When you're adding a new Controller to your app, you can use the new pagination checkbox to automatically generate the needed code:
-
Job Timeouts:
You can now configure a timeout for Job Workers:instance Job EmailCustomersJob where perform EmailCustomersJob { .. } = do customers <- query @Customer |> fetch forEach customers sendToCustomer where sendToCustomer customer = sendMail (MarketingMail customer) timeoutInMicroseconds = Just $ 1000000 * 60 -- 60 seconds
-
Added function to delete files from the cloud storage
You can now useremoveFileFromStorage
to remove uploaded files from S3 or any other configured cloud storage:action DeleteUploadedFileAction { uploadedFileId } = do uploadedFile <- fetch uploadedFile let storedFile = StoredFile { path = get #objectPath uploadedFile , url = get #url uploadedFile } removeFileFromStorage storedFile deleteRecord uploadedFile redirectTo UploadedFilesAction
-
Custom CORS policies
If you're building APIs with IHP you can now specify a custom CORS policy:-- Config.hs import qualified Network.Wai.Middleware.Cors as Cors config :: ConfigBuilder config = do option Development option (AppHostname "localhost") -- The boolean True specifies if credentials are allowed for the request. You still need to set withCredentials on your XmlHttpRequest option Cors.simpleCorsResourcePolicy { Cors.corsOrigins = Just (["localhost"], True) }
-
New Helper Function:
allEnumValues
Given a enum defined in theSchema.sql
like this:CREATE TYPE colors AS ENUM ('yellow', 'red', 'blue');
you can call
allEnumValues
to get a list of all the colors:let allColors = allEnumValues @Color -- allColors = [ Yellow, Red, Blue ]
This also works if you define custom type in
Web/Types.hs
that is derivingEnum
:data Color = Yellow | Red | Blue deriving (Enum) let allColors = allEnumValues @Color -- allColors = [ Yellow, Red, Blue ]
-
Respond with XML in your action:
We added a
renderXml
function:action MyVeryEnterprisyAction = do renderXml "<xml></xml>"
-
Added support for Unique Indices
You can now use
CREATE UNIQUE INDEX
statements inside yourSchema.sql
:CREATE UNIQUE INDEX users_index ON users (user_name);
Other Changes
- Fixed a crash in the Storage module
- Turned default.nix into a function by adding a param "pkgs" that defaults to . If nothing is passed in this should behave as ushall, but now the file can be imported using the nix builtin "import".
- Build .envrc already during ihp-new instead of on first project start
- IDE -> Data Editor: keep query in ace editor after submitting
- IDE -> Data Editor: Show sql errors in the same page instead of showing crash page
- Server-side Components: Fixed text fields and textareas not updating
- Updated jquery to 3.6.0
- Make sure that WebSocket.onClose is always called
- Added missing intances for TimeOfDay
- Support TSVector fields inside the database
- Form JS helpers: Use query parameters to transmit form values where the form is submitted via GET
- Fixed filterWhereIn and filterWhereNotIn not using IS NOT NULL when checking for null-ness of a field.
- Fixed imagemagick transforms not working as expected
- Prefix enum data constructors that would cause a compile-time error because of non-distinct naming with the type name
- Lot's of improvements to the documentation
- Use O1 by default for compiling Scripts in production, similiar how we do it with the background job worker
- Updated social links in the dev tools
- New documentation on joins
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.
v0.11.0 (Beta 13.06.2021)
A new IHP release with some new features (spoiler alert: finally table joins) and bug fixes. Around 150 commits have been merged since the last release 23 days ago 🚀
Major Changes
-
Joins:
This has been requested for quite some time already. Finally @hllizi solved this: You can now do joins with the IHP query builder 🎉tomPosts <- query @Post |> innerJoin @User (#authorId, #id) |> filterWhereJoinedTable @User (#name, "Tom") |> fetch
You can also join multiple tables:
query @Posts |> innerJoin @Tagging (#id, #postId) |> innerJoinThirdTable @Tag @Tagging (#id, #tagId) |> filterWhereJoinedTable @Tag (#tagText, "haskell") |> fetch
Type safety is maintained by adding all joined types to a type-level list and checking that the table has been joined where necessary.
E.g. uses offilterWhereJoinedTable @Model
will only compile if@Model
had been joined to the input before. -
Server-Side Components
We've mixed the ideas of react.js with HSX. IHP Server-Side Components provide a toolkit for building interactive client-side functionality without needing to write too much javascript.Here's how a Counter with a
Plus One
button looks like:module Web.Component.Counter where import IHP.ViewPrelude import IHP.ServerSideComponent.Types import IHP.ServerSideComponent.ControllerFunctions -- The state object data Counter = Counter { value :: !Int } -- The set of actions data CounterController = IncrementCounterAction deriving (Eq, Show, Data) $(deriveSSC ''CounterController) -- The render function and action handlers instance Component Counter CounterController where initialState = Counter { value = 0 } render Counter { value } = [hsx| Current: {value} <br /> <button onclick="callServerAction('IncrementCounterAction')">Plus One</button> |] action state IncrementCounterAction = do state |> incrementField #value |> pure instance SetField "value" Counter Int where setField value' counter = counter { value = value' }
Here's a demo of using Server-Side Components for a interactive data table:
Learn more about Server-Side Components in the documentation.
-
New IHP.FileStorage Module: For storing files in AWS S3 and other Cloud Storage Services
A common task when building web applications is to save and manage uploaded files like custom logos, profile pictures or
.csv
files provided by the user. IHP now provides a simple file storage system to upload files to Amazon S3 or any S3 compatible cloud service.Here's how the new API looks, once configured:
action UpdateCompanyAction { companyId } = do company <- fetch companyId company |> fill @'["name"] |> uploadToStorage #logoUrl >>= ifValid \case Left company -> render EditView { .. } Right company -> do company <- company |> updateRecord redirectTo EditCompanyAction { .. }
Some of the cool features:
- For pictures, use the ImageMagick preprocessor to resize and convert the pictures
- Generate signed download URLs for private files
- You can use the
static/
directory instead of uploading to S3 in development
-
Case Insensitive Operations:
You can now usefilterWhereCaseInsensitive
to query for strings in a case insensitive way:userByEmail :: Text -> IO (Maybe User) userByEmail email = do user <- query @User |> filterWhereCaseInsensitive (#email, email) |> fetchOneOrNothing -- Query: `SELECT * FROM users WHERE LOWER(email) = <email>` pure user
There's also a new
validateIsUniqueCaseInsensitive
for checking uniqueness in a case insensitive way:action CreateUserAction = do let user = newRecord @User user |> fill @'["email"] |> validateIsUniqueCaseInsensitive #email >>= ifValid \case Left user -> render NewView { .. } Right user -> do createRecord user redirectTo UsersAction
Important if you use IHP's Login: IHP's built-in sessions controller used for the built-in login now uses case-insensitive lookup for the email addresses at login. This will improve user experience for users that create their account with
[email protected]
and then try to log in using[email protected]
.
Other Changes
- Introduce redirectBackWithFallbackPath and redirectBackWithFallbackUrl
- Add
isTrue
andisFalse
Validation Constraints - Added basic support for CREATE FUNCTION sql statements in Schema.sql
- Added support for CREATE TRIGGER sql statements in Schema.sql
- Hide functions and unknown statements from the schema designer objects selector
- Use nonmoving gc in dev server
- Fixed migrate command not outputing the sql statements.
- Added support for more advanced postgres expression in check constraints
- Fixed context menu in schema designer not visible on small screen when having too many tables
- Added withCustomErrorMessageIO to customize validateIsUnique Error Message
- Added Delete All Rows Menu Option to the Data Editor
- Renamed
make ghci
tomake console
- Explicit type cast for UPDATE statements for array types
- Use applicationName When Generating Action And Mail
- Added FromJSON instance for Id's
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.
v0.10.0 (Beta 21.05.2021)
This is another release before our major version 1 :) It comes packed with a lot of small improvements and bug fixes 🚀
Major Changes
-
Auto Index Creation:
The Schema Designer will now automatically create an index when creating a column that has a foreign key constraint.E.g. when adding
user_id
column to aprojects
table, the schema designer will also automatically create an index on theuser_id
column. This speeds up queries with conditions on theuser_id
column, typically likequery @Project |> filterWhere (#userId, currentUserId) |> fetch
. In case you don't want an index for specific reasons, you can always manually remove the index again from yourSchema.sql
. -
Debounce File Changes:
Switching git branches triggers a lot of file changes on the IHP dev server. To avoid long reload times the dev server is debouncing file changes now. -
New Functions:
currentAdmin
andcurrentAdminOrNothing
:
LikecurrentUser
, but works when you also have aadmins
table next to your normalusers
table. -
Cache Busting:
Sometimes problems are caused when your users still have an old version of your JS or CSS files inside their browsers cache. To avoid this you typically append a hash to the url of your JS or CSS file.IHP now provides an
assetPath
view helper to deal with this:[hsx| <script={assetPath "/app.js"}/> <link rel="stylesheet" href={assetPath "/app.css"}/> |]
The paths will look like this
"/app.js?v=9be8995c-7055-43d9-a1b2-43e05c210271"
.IHP will set the asset version from the
IHP_ASSET_VERSION
env variable. This should be set to e.g. your git commit hash in production. In development you don't need to specify this environment variable. If you run on IHP Cloud, it works out of the box. -
Built-in Background Jobs Dashboard:
@zacwood9 added a new very cool dashboard to schedule and manage your IHP background jobs without manually writing lots of CRUD code.
Check the new Guide section to get started -
Code of Conduct:
The IHP community now follows the Guidelines for Respectful Communication by the Haskell Foundation. -
Improved Form Customization:
We added a newformForWithOptions
and also a shortcutformForWithoutJavascript
to allow advanced form configuration. This specifically addresses the issue that previously there was no way to disable the javascript form submission for a specific form. Check out the new guide section to learn more. -
New Function:
filterWhereNot
LikefilterWhere
but negated:projectsByUser :: UserId -> IO [Project] projectsByUser userId = do otherProjects <- query @Project |> filterWhereNot (#userId, userId) |> fetch -- Query: `SELECT * FROM projects WHERE user_id != <userId>` return otherProjects
-
New Function:
modifyJust
Likemodify
, but only modifies the value if it's notNothing
.let pauseDuration = now `diffUTCTime` pausedAt floorTimer <- floorTimer |> modifyJust #startedAt (addUTCTime pauseDuration) |> updateRecord
Other Changes
- Update Foreign Key Reference When Column Is Updated
- use correct pragma in generated code
- Fixed code generator for job workers not working as expected
- Inside your project's
default.nix
it's now possible to request haddock docs for your haskell packages - Added modelContext constraint to WSApp to allow easy database querying from websocket servers
- Ensure that new and updated Enum Values are non-empty and unique when added to the schema
- Add support for
Integer
ID types in AutoRoute - Fixed background workers not started by dev server when doing local development
- Hide "NOTICE: relation schema_migrations already exists, skipping" notice when running migrations.
- Added Person/People Plural/Singular Support
- Abort if the dev server is started as root, running as root breaks the built-in postgres server
- Added type signature for generated index view code
- Use exception tracker in ErrorController.displayException to log handled exceptions to sentry as well
- Fixed custom 404 pages not having 404 status code
- Added common jumpToAction issue to docu
- Improved error message when not all arguments are passed to a controller action
- exampleModalLabel -> modal-title
- Added
deleteSession
andgetSessionRecordId
functions - HSX: boolean data-attributes now use true/false values instead of behaving like HTML boolean attributes
- Fixed auto generated ParamReader instances missing implementation for readParameterJSON
- Allow NaN in Postgres Point Type
- Fixed turbolink redirect not working as expected after form submission.
- Make the full app config from Config.hs available through FrameworkConfig.appConfig
- Allow isDevelopment/isProduction to be used in mail's to and from functions by making ?context available.
- Disable nonmoving-gc for now as it causes server crashes when running binaries in optimized mode
- Use improved nix installer on mac
- Link To Show Table In Schema
- Rename IHP.HtmlSupport Modules To IHP.HSX
- Added Trailing slash for additional apps. e.g. now href=localhost:8000/admin/ (trailing slash required). Previously link broken in IDE without a trailing slash, similar for other autogenerated and autorouted applications.
- Remove forceRedirectToPath as we now have a better way to disable JS helpers per form (see form documentation).
- Display errors before warnings inside devtools.
- Fixed code generators not finding the right table if word is not english
- Many more small improvements to the Guide and API docs
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..
v0.9.0 (Beta 26.02.2021)
A lot has been done since the last IHP release in january. With v0.9 we're now getting closer to the first major version of IHP 🎉
Major Changes
-
Use the new GHC Garbage Collector: We now use the new non-moving GC which has been added in recent GHC versions. This avoids long GC pauses when it's time to free some memory. This will improve the production performance.
-
New Logging module:
IHP was previously usingputStrLn
to log something. This has been replaced with a newIHP.Log
modules that provides a unified logging solution for the full framework. The logger supports custom formatters and also custom log destinations (e.g. logging to a file instead of stdout/stderr).Check the full documentation here: https://ihp.digitallyinduced.com/Guide/logging.html
-
New Plugins: ihp-sentry and ihp-zip
Add error reporting to your IHP apps with ihp-sentry
Provides easy zip file downloads with ihp-zip
Check the plugin's README for details on how to install them.If you're thinking about making your own IHP plugin, consider using these as the starting point :)
-
Custom WebSocket Servers:
We now opened up the IHP WebSocket interface so that you can write your own WebSocket servers.Check out the new Guide on WebSockets: https://ihp.digitallyinduced.com/Guide/websockets.html
-
AutoRoute Improvements:
In previous IHP versions AutoRoute by default only worked withUUID
arguments (and Id types likeId Post
). If you have a controller likedata HelloWorldController = HelloAction { name :: Text }
where you have a text parameter, you would have to configure AutoRoute manually usingparseArgument
. Also when we you had an action with two different types likeHelloAction { userId :: Id User, name :: Maybe Text }
this was not supported by AutoRoute.The type-magic has been reengineered and now it works with all types especially different types. If you have used
parseArgument
before, you need to remove that as these functions have been removed because this now works without manually configuring it. -
Database Transactions:
There's a newwithTransaction
function to run sql statements inside a single database transaction:withTransaction do company <- newRecord @Company |> createRecord user <- newRecord @User |> set #companyId (get #id company) |> createRecord company <- company |> setJust #ownerId (get #id user) |> updateRecord
Check out the Guide for details: https://ihp.digitallyinduced.com/Guide/database.html#transactions
-
New Function:
setJust
:
Likeset
but wraps the value in aJust
. Across a lot of IHP code bases we spotted a pattern like|> set #field (Just(value))
. The(Just ..)
is basically just syntactical noise and makes the value expression kind of more complicated:-- BEFORE project |> set #userId (Just (get #id user)) -- AFTER project |> setJust #userId (get #id user)
-
New Query Function:
distinct
: Usedistinct
to remove all duplicate rows from the resultquery @Book |> distinct |> fetch -- SELECT DISTINCT * FROM books
-
New Query Function:
distinctOn
: UsedistinctOn
to add aDISTINCT ON
to your sql query.query @Book |> distinctOn #categoryId |> fetch -- SELECT DISTINCT ON (category_id) * FROM books
-
New Query Functions:
filterWhereLike
and friends:query @Book |> filterWhereLike (#title, "%haskell%") |> fetch -- SELECT * FROM books WHERE title LIKE '%haskell%'
Additonally there's now also:
-filterWhereILike
(likefilterWhereLike
but case-insensitive)
-filterWhereMatches
filter with a postgres regex
-filterWhereIMatches
case-insensitive variant offilterWhereMatches
Other Changes
- added empty array '{}' in default-dropdown for array columns in IDE
- added support for
smallint
and brought existing support forbigint
andsmallint
to the schema designer - Fixed a bug where IHP Telemetry was not correctly detecting windows if it was running in WSL2
- Column types are now categorized in the schema designer
- Added postgres inet type
- Added support for CREATE INDEX statements in Schema.sql
- Support X-Real-IP header in logs
- Simplified query builder by replacing GADT with a more boring data structure
- Moved fetch family of functions from IHP.QueryBuilder to a new IHP.Fetch
- Ignore Globally Installed Packages When Loading GHCI
- Added more SVG elements to hsx whitelist
- Allow hsx to parse spaces in closing tags
- Fix type generation fails if empty enum is given.
- Fixed buggy space stripping in HSX
- Add
make postgres
command to start only the postgres server - Fixed race condition caused by auto refresh updates during form submission
- Fixes Job generator always generating to Web
- Added jobs documentation
- the data tool now has pagination
- Added
forceRedirectToPath
to work around JS issues - Support Multi-column Indexes
- Added setTitle and pageTitle for a standard <title> management
- Added logging of database query times.
- Introduce Custom
ihp:load
Javascript Event - didChange should not return True when the value is the same as the database value
- Fixed isActivePath not taking query string into account
getRequestBody
now works for JSON requests as well- Table indexes are shown in the schema designer now
- Enabled link preloading by default again, it got lost in a previous release
- Forms: added support for autofocus
- Many more small improvements to the Guide and API docs
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..
📅 The next release is expected to be available in march.