Ihp Versions Save

πŸ”₯ The fastest way to build type safe web apps. IHP is a new batteries-included web framework optimized for longterm productivity and programmer happiness

v0.17.0

2 years ago

IHP v0.17.0 is out now

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:

    image

  • πŸ‘¨β€πŸ’» Optional HSX Attributes: HSX gained special handling for Maybe 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 the target attribute not being in the output HTML:

    let
        target :: Maybe Text
        target = Nothing
    in
        [hsx|<a target={target} />|]
    

    This will render to:

    <a />
    
  • :atom: 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 https://github.com/haskell/haskell-language-server/issues/2340

  • πŸ“« Encrypted SMTP When configuring the IHP Mailer to use SMTP, you can now specify the encryption by setting the encryption field:

    import IHP.Mail
    
    config :: ConfigBuilder
    config = do
        option $ SMTP
            { host = "smtp.myisp.com"
            , port = 2525
            , credentials = Nothing
            , encryption = STARTTLS -- <-- NEW
            }
    

Other Changes

Full Changelog: https://github.com/digitallyinduced/ihp/compare/v0.16.0...v0.17.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.

v0.16.0

2 years ago

IHP v0.16.0 is out now

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 run nix-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 server
    • RunJobs, if you’re using the IHP job queue, this binary will be the entrypoint for the workers a binary for each script in Application/Script, e.g. Welcome for Application/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 and envOrNothing 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"
    

    Learn more in the new configuration docs

  • SchemaDesigner: Support for Postgres Policies You can now create and manage Postgres Policies inside the Schema Designer:

    image

    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 :)

    image

    Check out the docs if you want to have your own custom 404 page.

Other Changes

New Contributors

Full Changelog: https://github.com/digitallyinduced/ihp/compare/v0.15.0...v0.16.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.

v0.15.0

2 years ago

IHP v0.15.0 is out now

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
    

    Learn how to integration Stripe in the Documentation

  • πŸ“¦ 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'
    

    Learn how to deploy with Docker

  • πŸ•ΈοΈ 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
    
        -- ...
    

    Learn how to manage OG meta tags

  • 🌐 HTML in Validation Error Messages: You can now use attachFailureHtml 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 Bildschirmfoto 2021-10-18 um 15 39 01

  • 🎨 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 image

  • πŸ’½ 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 the new-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 the Application/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>
                <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: [email protected] Password: demo). The app is real-time, try to open it in two browsers and change something :)

    The source code is available in the ihp-datasync-demo repo.

Other Changes

Pull Requests

New Contributors

Full Changelog: https://github.com/digitallyinduced/ihp/compare/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.

v0.14.0

2 years ago

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 a run_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 πŸ’˜ IHP

  • 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 your Config/Config.hs.

    Learn more about this in the docs.

  • πŸ“« 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.

    Check out the documentation on how to set this up

  • 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 to createTemporaryDownloadUrlFromPath, but you can pass a custom expiration time. The createTemporaryDownloadUrlFromPath 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 at localhost:8000. This is a problem in services like GitPod or GitHub spaces, where the the host might not be localhost.

    There are now two env variables IHP_BASEURL and IHP_IDE_BASEURL to override the BaseUrl configured in Config/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.

    Bildschirmfoto 2021-08-27 um 15 51 52

Other Changes

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

2 years ago

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

2 years ago

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:

    let externalUrl = "http://example/picture.jpg"
    
    let options :: StoreFileOptions = def
            { directory = "pictures"
            }
    
    storedFile <- storeFileFromUrl externalUrl options
    let newUrl = get #url storedFile
    

    Learn more in the documentation.

  • New Function: storeFileFromPath Quickly upload something from the current app server into your S3 bucket:

    let options :: StoreFileOptions = def
            { directory = "pictures"
            }
    
    storedFile <- storeFileFromPath "picture.jpg" options
    let newUrl = get #url storedFile
    

    Learn more in the documentation.

  • 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 Bildschirmfoto 2021-08-06 um 16 16 13

  • 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>" }
        ]
    

    Learn more in the Guide

  • New View Helper: time Similiar to timeAgo, but always showing an absolute time. Learn more.

  • New icons in the Schema Designer Bildschirmfoto 2021-08-06 um 16 13 59

  • 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

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

2 years ago

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:

    image

    When you're adding a new Controller to your app, you can use the new pagination checkbox to automatically generate the needed code:

    image

  • 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
    

    See the documentation for details

  • Added function to delete files from the cloud storage You can now use removeFileFromStorage 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 the Schema.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 deriving Enum:

    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 your Schema.sql:

    CREATE UNIQUE INDEX users_index ON users (user_name);
    

Other Changes

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

2 years ago

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 of filterWhereJoinedTable @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' }
    

    Bildschirmvideo aufnehmen 2021-06-13 um 14 07 57

    Here's a demo of using Server-Side Components for a interactive data table:

    Bildschirmvideo aufnehmen 2021-06-13 um 14 11 05

    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

    Learn more in the documentation.

  • Case Insensitive Operations: You can now use filterWhereCaseInsensitive 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

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

2 years ago

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 a projects table, the schema designer will also automatically create an index on the user_id column. This speeds up queries with conditions on the user_id column, typically like query @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 your Schema.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 and currentAdminOrNothing: Like currentUser, but works when you also have a admins table next to your normal users 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 new formForWithOptions and also a shortcut formForWithoutJavascript 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 Like filterWhere 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 Like modify, but only modifies the value if it's not Nothing.

    let pauseDuration = now `diffUTCTime` pausedAt
    
    floorTimer <- floorTimer
            |> modifyJust #startedAt (addUTCTime pauseDuration)
            |> updateRecord
    

Other Changes

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

3 years ago

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 using putStrLn to log something. This has been replaced with a new IHP.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 with UUID arguments (and Id types like Id Post). If you have a controller like data HelloWorldController = HelloAction { name :: Text } where you have a text parameter, you would have to configure AutoRoute manually using parseArgument. Also when we you had an action with two different types like HelloAction { 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 new withTransaction 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: Like set but wraps the value in a Just. 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: Use distinct to remove all duplicate rows from the result

    query @Book
        |> distinct
        |> fetch
    
    -- SELECT DISTINCT * FROM books
    
  • New Query Function: distinctOn: Use distinctOn to add a DISTINCT 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 (like filterWhereLike but case-insensitive) - filterWhereMatches filter with a postgres regex - filterWhereIMatches case-insensitive variant of filterWhereMatches

Other Changes

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.