Skip to content

Releases: digitallyinduced/ihp

v0.17.0 (Beta 14.12.2021)

14 Dec 10:23
Compare
Choose a tag to compare

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 haskell/haskell-language-server#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

Read more

v0.16.0 (Beta 06.11.2021)

06 Nov 14:05
587cb6c
Compare
Choose a tag to compare

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: v0.15.0...v0.16.0

Feature Voting

[Help decide what's coming next to IHP by using the Feature Voting!](https://ihp.digitallyinduce...

Read more

v0.15.0 (Beta 18.10.2021)

18 Oct 16:49
Compare
Choose a tag to compare

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>
                <AppNav...
Read more

v0.14.0 (Beta 27.08.2021)

27 Aug 14:44
Compare
Choose a tag to compare

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

10 Aug 08:45
Compare
Choose a tag to compare

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)

06 Aug 14:34
Compare
Choose a tag to compare

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 (Beta 10.07.2021)

10 Jul 12:26
Compare
Choose a tag to compare

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 (Beta 13.06.2021)

13 Jun 13:06
Compare
Choose a tag to compare

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 (Beta 21.05.2021)

21 May 17:25
Compare
Choose a tag to compare

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 (Beta 26.02.2021)

26 Feb 16:57
883cf75
Compare
Choose a tag to compare

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.