Skip to content

Commit

Permalink
Merge branch 'master' into background-jobs
Browse files Browse the repository at this point in the history
  • Loading branch information
mpscholten authored Dec 11, 2020
2 parents ce2973b + 17deb8a commit e387c6b
Show file tree
Hide file tree
Showing 69 changed files with 1,655 additions and 998 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/linux_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
echo "\$file_contents = file_get_contents('./default.nix');" >> replace.php
echo '$new_content = preg_replace("/\".{40}\"/", "\"$argv[1]\"", $file_contents);' >> replace.php
echo '$new_content_with_ref = preg_replace("/ihp = builtins.fetchGit {/", "ihp = builtins.fetchGit { ref = \"*\";", $new_content);' >> replace.php
echo '$new_content_with_ref = str_replace("p.ihp", "p.ihp wreq mmark mmark-ext strip-ansi-escape stripe-signature stripe-concepts http-conduit", $new_content_with_ref);' >> replace.php
echo '$new_content_with_ref = str_replace("p.ihp", "p.ihp wreq mmark mmark-ext strip-ansi-escape stripe-signature stripe-concepts http-conduit units units-defs", $new_content_with_ref);' >> replace.php
echo "file_put_contents('./default.nix', \$new_content_with_ref);" >> replace.php
echo "?>" >> replace.php
php replace.php $GITHUB_SHA
Expand Down
2 changes: 2 additions & 0 deletions Guide/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ HTML_FILES+= design-goals.html
HTML_FILES+= troubleshooting.html
HTML_FILES+= modal.html
HTML_FILES+= database-migrations.html
HTML_FILES+= tailwindcss.html
HTML_FILES+= npm.html

all: $(HTML_FILES) bootstrap.css instantclick.js

Expand Down
88 changes: 43 additions & 45 deletions Guide/architecture.markdown
Original file line number Diff line number Diff line change
@@ -1,40 +1,35 @@
# Architecture


```toc
```
```

This section tries to answer common questions on where to place your code. These are recommendations found by digitally induced to be working well.

In general remember that all specific web app logic should stay in the `Web/` space. The `Application/` space is for sharing code across all your different applications. E.g. code shared between your web application and your admin backend.
This section answers common questions regarding where to place your code. These recommendations are found to be working well at digitally induced.

In general, remember that all specific web app logic should stay in the `Web/` space. The `Application/` space is for sharing code across all your different applications. E.g. code shared between your web application and your admin back-end.

## Directory Structure


| File or Directory | Purpose |
|-------------------------------|-----------------------------------------------------------------------------|
| Config/ | |
| Config/Config.hs | Configuration for the framework and your application |
| Config/nix/nixpkgs-config.nix | Configuration for the nix package manager |
| Config/nix/haskell-packages/ | Custom Haskell dependencies can be placed here |
| Application/ | Your domain logic lives here |
| Application/Schema.sql | Models and database tables are defined here |
| Web/Controller | Web application controllers |
| Web/View/ | Web application html template files |
| Web/Types.hs | Central place for all web application types |
| static/ | Images, css and javascript files |
| .ghci | Default config file for the Haskell interpreter |
| .gitignore | List of files to be ignored by git |
| App.cabal, Setup.hs | Config for the cabal package manager |
| default.nix | Declares your app dependencies (like package.json for NPM or composer.json for PHP) |
| Makefile | Default config file for the make build system |

| File or Directory | Purpose |
| ----------------------------- | ----------------------------------------------------------------------------------- |
| Config/ | |
| Config/Config.hs | Configuration for the framework and your application |
| Config/nix/nixpkgs-config.nix | Configuration for the Nix package manager |
| Config/nix/haskell-packages/ | Custom Haskell dependencies can be placed here |
| Application/ | Your domain logic lives here |
| Application/Schema.sql | Models and database tables are defined here |
| Web/Controller | Web application controllers |
| Web/View/ | Web application HTML template files |
| Web/Types.hs | Central place for all web application types |
| static/ | Images, CSS, and JavaScript files |
| .ghci | Default config file for the Haskell interpreter |
| .gitignore | List of files to be ignored by git |
| App.cabal, Setup.hs | Config for the cabal package manager |
| default.nix | Declares your app dependencies (like package.json for NPM or composer.json for PHP) |
| Makefile | Default config file for the make build system |

## FAQ


##### Where to place a function I want to use in all my views?

If the function is only used in a single application and is a building block for your layout, place it in `Web/View/Layout.hs`. The module is already imported in all your views (just don't forget to add the function to the export list).
Expand All @@ -49,15 +44,15 @@ Place it in `Application/Helper/Controller.hs`. This module is already imported

Place it in `Web/Types.hs`.

##### Next to my main web application, I'm building an admin backend application. Where to place it?
##### Next to my main web application, I'm also building an admin back-end application. Where to place it?

A IHP project can consist of multiple applications. Run `new-application admin` to generate a new admin application. The logic for the new application is located in the `Admin/` directory. On the web you can find it at `http://localhost:8000/admin/` (all actions are prefixed with `/admin/`).
An IHP project can consist of multiple applications. Run `new-application admin` inside a `nix-shell` to generate a new admin application. The logic for the new application is located in the `Admin/` directory. You can find it on the web at `http://localhost:8000/admin/` (all actions are prefixed with `/admin/`).

##### How to structure my CSS?

CSS files, as all your other static assets, should be placed in the `static` directory.

Create a `static/app.css`. In there use CSS imports to import your other stylesheets. An example `app.css` could look like this:
Create a `static/app.css`. In there use CSS imports to import your other style sheets. An example `app.css` could look like this:

```css
@import "/layout.css";
Expand All @@ -69,7 +64,7 @@ Create a `static/app.css`. In there use CSS imports to import your other stylesh

###### Page-specific CSS rules

Place page-specific CSS used by e.g. views of the `Web.Controller.Users` controller in `users.css`. Use [currentViewId](https://ihp.digitallyinduced.com/api-docs/IHP-ViewSupport.html#v:currentViewId) to scope your css rules to the view.
Place page-specific CSS used by e.g. views of the `Web.Controller.Users` controller in `users.css`. Use [currentViewId](https://ihp.digitallyinduced.com/api-docs/IHP-ViewSupport.html#v:currentViewId) to scope your CSS rules to the view.

Given the view:

Expand All @@ -88,30 +83,33 @@ This will render like
```html
<div id="projects-show">
<h1>Hello World!</h1>
</div>`
</div>
`
```

So in your `projects.css` you can just do rules like
So in your `projects.css` you can just do rules like

```css
#projects-show h1 { color: blue; }
#projects-show h1 {
color: blue;
}
```

###### SASS & Webpack

We discourage the use of tools like SASS or webpack because they have too much overhead.
We discourage the use of tools like SASS or Webpack because they have too much overhead.

###### Library CSS

CSS files from external libraries or components should be placed in `static/vendor/`.

##### How to structure my Javascript Code?
##### How to structure my JavaScript Code?

JS files, as all your other static assets, should be place in the `static` directory.
JavaScript files, as well as your other static assets, should be placed in the `static` directory.

In general we follow an approach where most of the business logic resides on the Haskell server. Only for small interactions, or client-side GUI niceness, we try to use a small isolated bit of javascript.
In general, we follow an approach where most of the business logic resides on the Haskell server. Only for small interactions, or client-side GUI niceness, we try to use a small isolated bit of JavaScript.

Your global, non-page specific, javascript code can be placed in `app.js`.
Your global, non-page specific, JavaScript code can be placed in `app.js`.

E.g. the `app.js` could look like this:

Expand All @@ -131,22 +129,22 @@ In your `Web.View.Layout` just import the `app.js`:
<script src="/app.js"></script>
```

###### Page-specific JS
###### Page-specific JavaScript

Place page-specific JS used by e.g. views of the `Web.Controller.Users` controller in the `users.js`.
Place page-specific JavaScript used by e.g. views of the `Web.Controller.Users` controller in the `users.js`.

In the views, just import the javascript with `<script src="/users.js"></script>`.
In the views, just import the JavaScript with `<script src="/users.js"></script>`.

###### Webpack

We discourage the use of webpack or any other bundler because they have too much overhead. Of course this advice only applies if you follow the approach to use as little javascript as possible.
We discourage the use of Webpack or any other bundler because they have too much overhead. Of course, this advice only applies if you follow the approach to use as little JavaScript as possible.

###### Library JS
###### Library JavaScript

JS files from external libraries or components should be placed in `static/vendor/`. For simplicity it might make sense to just download the javascript bundle of the library you want to use, and then just commit it into git instead of using NPM.
JavaScript files from external libraries or components should be placed in `static/vendor/`. For simplicity, it might make sense to just download the JavaScript bundle of the library you want to use, and then just commit it into git instead of using NPM.

For more complex use-cases with lots of javascript, you should not follow this advice and just use NPM instead.
For more complex use-cases with lots of JavaScript, you should not follow this advice and just use NPM instead.

##### Where to place static images?

Place your images in the `static` folder. We recommend to use SVG images.
Place your images in the `static` folder. We recommend using SVG images.
36 changes: 17 additions & 19 deletions Guide/authentication.markdown
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
# Authentication

```toc
```

## Quick-and-Dirty: HTTP Basic Auth

While IHP provides an authentication toolkit out of the box, it also provides a shortcut for cases where you just want the simplest possible way to enforce a hard-coded username/password before accessing your new web application. This shortcut leverages HTTP Basic Authentication built in to all browsers:
While IHP provides an authentication toolkit out of the box, it also provides a shortcut for cases where you just want the simplest possible way to enforce a hard-coded username/password before accessing your new web application. This shortcut leverages HTTP Basic Authentication built into all browsers:

```haskell
instance Controller WidgetsController where
beforeAction = basicAuth "sanja" "hunter2" "myapp"
```

The parameters are: username, password and authentication realm. The realm can be thought of as an area of validity for the credentials. It is common to put the project name, but it can also be blank (meaning the entire domain).

The parameters are: username, password, and authentication realm. The realm can be thought of as an area of validity for the credentials. It is common to put the project name, but it can also be blank (meaning the entire domain).

## Introduction - Real Authentication

The usual convention in IHP is to call your user record `User`. When there is an admin user, we usually call the record `Admin`. In general the authentication can work with any kind of record. The only requirement is that it has an id field.
The usual convention in IHP is to call your user record `User`. When there is an admin user, we usually call the record `Admin`. In general, the authentication can work with any kind of record. The only requirement is that it has an id field.

To use the authentication module, your `users` table needs to have at least an `id`, `email`, `password_hash`, `locked_at` and `failed_login_attempts` field. Add this to `Schema.sql`:

```sql
CREATE TABLE users (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY NOT NULL,
Expand All @@ -38,12 +39,11 @@ If you are creating an admin sub-application, first use the code generator to cr

## Setup

Currently the authentication toolkit has to be enabled manually. We plan to do this setup using a code generator in the future.

Currently, the authentication toolkit has to be enabled manually. We plan to do this setup using a code generator in the future.

#### Controller Helpers

First we need to enable the controller helpers. Open `Application/Helper/Controller.hs`. It will look like this:
First, we need to enable the controller helpers. Open `Application/Helper/Controller.hs`. It will look like this:

```haskell
module Application.Helper.Controller (
Expand Down Expand Up @@ -72,11 +72,9 @@ import Generated.Types
type instance CurrentUserRecord = User
```



#### View Helpers

Additionally we also need to enable the view helpers. Open `Application/Helper/View.hs`. It will look like this:
Additionally, we also need to enable the view helpers. Open `Application/Helper/View.hs`. It will look like this:

```haskell
module Application.Helper.View (
Expand Down Expand Up @@ -122,7 +120,7 @@ instance FrontController WebApplication where
]
```

At the end of the file there is a line like:
At the end of the file, there is a line like:

```haskell
instance InitControllerContext WebApplication
Expand All @@ -140,9 +138,9 @@ This will fetch the user from the database when a `userId` is given in the sessi

#### Adding a Session Controller

In the last step we need to add a new controller which will deal with the login and logout. We call this the `SessionsController`.
In the last step, we need to add a new controller that will deal with the login and logout. We call this the `SessionsController`.

First we have to update `Web/Types.hs`. The auth module directs users to the login page automatically if required by a view, to set this up we add the following to `Web/Types.hs`:
First, we have to update `Web/Types.hs`. The auth module directs users to the login page automatically if required by a view, to set this up we add the following to `Web/Types.hs`:

```haskell
import IHP.LoginSupport.Types
Expand All @@ -151,7 +149,7 @@ instance HasNewSessionUrl User where
newSessionUrl _ = "/NewSession"
```

You also need to add the type definitions for the SessionsController:
You also need to add the type definitions for the `SessionsController`:

```haskell
data SessionsController
Expand Down Expand Up @@ -184,7 +182,7 @@ instance Controller SessionsController where
instance Sessions.SessionsControllerConfig User where
```

Additionally we need to implement a login view at `Web/View/Sessions/New.hs` like this:
Additionally, we need to implement a login view at `Web/View/Sessions/New.hs` like this:

```haskell
module Web.View.Sessions.New where
Expand Down Expand Up @@ -225,7 +223,7 @@ renderForm user = [hsx|
After you have completed the above steps, you can open the login at `/NewSession`. You can generate a link to your login page like this:

```html
<a href={NewSessionAction}>Login</a>
<a href="{NewSessionAction}">Login</a>
```

## Accessing the current user
Expand Down Expand Up @@ -257,15 +255,15 @@ You can also access the user using `currentUser` inside your views:

```html
[hsx|
<h1>Hello {get #email currentUser}</h1>
<h1>Hello {get #email currentUser}</h1>
|]
```

## Logout

You can simply render a link inside your layout or view to send the user to the logout:

```html
```haskell
<a class="js-delete js-delete-no-confirm" href={DeleteSessionAction}>Logout</a>
```

Expand All @@ -281,7 +279,7 @@ To create a user with a hashed password, you just need to call the hashing funct
|> validateField #email isEmail
|> validateField #passwordHash nonEmpty
|> ifValid \case
Left user -> render NewView { .. }
Left user -> render NewView { .. }
Right user -> do
hashed <- hashPassword (get #passwordHash user)
user <- user
Expand Down
14 changes: 7 additions & 7 deletions Guide/authorization.markdown
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Authorization

```toc
```

## Restricting an action to logged in users
## Restricting an action to logged-in users

To restrict an action to a logged in user, use `ensureIsUser`:
To restrict an action to a logged-in user, use `ensureIsUser`:

```haskell
action PostsAction = do
Expand All @@ -14,9 +15,9 @@ action PostsAction = do
render IndexView { .. }
```

When someone is trying to access the `PostsAction` but is not logged in, the browser will be redirected to the login page. After the login succeeded, the user will be redirected back to the `PostsAction`.
When someone is trying to access the `PostsAction` but is not logged-in, the browser will be redirected to the login page. After the login succeeded, the user will be redirected back to the `PostsAction`.

It's common to restrict all actions inside a controller to logged in users only. Place the `ensureIsUser` inside the `beforeAction` hook to automatically apply it to all actions:
It's common to restrict all actions inside a controller to logged-in users only. Place the `ensureIsUser` inside the `beforeAction` hook to automatically apply it to all actions:

```haskell
instance Controller PostsController where
Expand All @@ -33,9 +34,9 @@ instance Controller PostsController where

In this case `PostsAction` and `ShowPostAction` are only accessible to logged-in users.

## Restricting an action to logged in admins
## Restricting an action to logged-in admins

To restrict an action to a logged in admin, use `ensureIsAdmin` instead of `ensureIsUser`. If you get
To restrict an action to a logged-in admin, use `ensureIsAdmin` instead of `ensureIsUser`. If you get

```
error:
Expand All @@ -54,7 +55,6 @@ instance Controller UserController where
ensureIsAdmin @Admin
```


## Checking for Permissions

You can use `accessDeniedUnless` to allow certain things only for specific users. For example, to restrict a `ShowPostAction` only to the user who a post belongs to, use this:
Expand Down
Loading

0 comments on commit e387c6b

Please sign in to comment.