utopia 2.30.2 → 2.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/bake/utopia/server.rb +1 -1
- data/bake/utopia/site.rb +3 -3
- data/context/getting-started.md +93 -0
- data/context/index.yaml +32 -0
- data/context/integrating-with-javascript.md +75 -0
- data/context/middleware.md +157 -0
- data/context/server-setup.md +116 -0
- data/context/updating-utopia.md +69 -0
- data/context/what-is-xnode.md +41 -0
- data/lib/utopia/content/document.rb +39 -37
- data/lib/utopia/content/link.rb +1 -2
- data/lib/utopia/content/links.rb +2 -2
- data/lib/utopia/content/markup.rb +10 -10
- data/lib/utopia/content/middleware.rb +195 -0
- data/lib/utopia/content/namespace.rb +1 -1
- data/lib/utopia/content/node.rb +1 -1
- data/lib/utopia/content/response.rb +1 -1
- data/lib/utopia/content/tags.rb +1 -1
- data/lib/utopia/content.rb +4 -186
- data/lib/utopia/controller/actions.md +8 -8
- data/lib/utopia/controller/actions.rb +1 -1
- data/lib/utopia/controller/base.rb +4 -4
- data/lib/utopia/controller/middleware.rb +133 -0
- data/lib/utopia/controller/respond.rb +2 -46
- data/lib/utopia/controller/responder.rb +103 -0
- data/lib/utopia/controller/rewrite.md +2 -2
- data/lib/utopia/controller/rewrite.rb +1 -1
- data/lib/utopia/controller/variables.rb +11 -5
- data/lib/utopia/controller.rb +4 -126
- data/lib/utopia/exceptions/mailer.rb +4 -4
- data/lib/utopia/extensions/array_split.rb +2 -2
- data/lib/utopia/extensions/date_comparisons.rb +3 -3
- data/lib/utopia/import_map.rb +374 -0
- data/lib/utopia/localization/middleware.rb +173 -0
- data/lib/utopia/localization/wrapper.rb +52 -0
- data/lib/utopia/localization.rb +4 -202
- data/lib/utopia/path.rb +26 -11
- data/lib/utopia/redirection.rb +2 -2
- data/lib/utopia/session/lazy_hash.rb +1 -1
- data/lib/utopia/session/middleware.rb +218 -0
- data/lib/utopia/session/serialization.rb +1 -1
- data/lib/utopia/session.rb +4 -205
- data/lib/utopia/static/local_file.rb +19 -19
- data/lib/utopia/static/middleware.rb +120 -0
- data/lib/utopia/static/mime_types.rb +1 -1
- data/lib/utopia/static.rb +4 -108
- data/lib/utopia/version.rb +1 -1
- data/lib/utopia.rb +1 -0
- data/readme.md +7 -0
- data/releases.md +7 -0
- data/setup/site/config.ru +1 -1
- data.tar.gz.sig +0 -0
- metadata +31 -4
- metadata.gz.sig +0 -0
- data/lib/utopia/locale.rb +0 -29
- data/lib/utopia/responder.rb +0 -59
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3268818f2c2b09e4d80b6d74884adfd2e1cfd60762250a6d7077465cd4f4999d
|
|
4
|
+
data.tar.gz: f9bc6fb2a4e9548b2f8f47b8bf691877aa4e55cc866f044ddc2147d01077a3f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 26d27ec806a779ea012758473f2e9c72a66042a3db31bd1a7e66855f5ce60d81ba765f3aa5292f36abbe959f5dfb8b47e8f0211ec31a8475c22f656f3f4343f4
|
|
7
|
+
data.tar.gz: '069d1e930246601028c078d129bcdf11d8f44d8f441e6f7a11be37162e12e2676520e56d57ed4013974120f7c58f07e87c61f933d7c49ec3e99c564b408d8c46'
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/bake/utopia/server.rb
CHANGED
|
@@ -45,7 +45,7 @@ def update(root: context.root)
|
|
|
45
45
|
# chmod -R g+w . # Change permissions
|
|
46
46
|
# chmod g-w .git/objects/pack/* # Git pack files should be immutable
|
|
47
47
|
# chmod g+s `find . -type d` # New files get group id of directory
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
# Set some useful defaults for the environment.
|
|
50
50
|
recipe = context.lookup("utopia:environment:update")
|
|
51
51
|
recipe.instance.update("environment", root: root) do |store|
|
data/bake/utopia/site.rb
CHANGED
|
@@ -57,7 +57,7 @@ def create(root: context.root)
|
|
|
57
57
|
|
|
58
58
|
if File.exist?(destination_path)
|
|
59
59
|
buffer = File.read(destination_path).gsub("$UTOPIA_VERSION", Utopia::VERSION)
|
|
60
|
-
File.open(destination_path, "w") {
|
|
60
|
+
File.open(destination_path, "w") {|file| file.write(buffer)}
|
|
61
61
|
else
|
|
62
62
|
Console.warn(self) {"Could not open #{destination_path}, maybe it should be removed from CONFIGURATION_FILES?"}
|
|
63
63
|
end
|
|
@@ -119,11 +119,11 @@ def upgrade(root: context.root)
|
|
|
119
119
|
|
|
120
120
|
FileUtils.copy_entry(source_path, destination_path)
|
|
121
121
|
buffer = File.read(destination_path).gsub("$UTOPIA_VERSION", Utopia::VERSION)
|
|
122
|
-
File.open(destination_path, "w") {
|
|
122
|
+
File.open(destination_path, "w") {|file| file.write(buffer)}
|
|
123
123
|
end
|
|
124
124
|
|
|
125
125
|
context.lookup("utopia:environment:setup").call(root: root)
|
|
126
|
-
|
|
126
|
+
|
|
127
127
|
# Stage any files that have been changed or removed:
|
|
128
128
|
system("git", "add", "-u", chdir: root) or fail "could not add files"
|
|
129
129
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Getting Started
|
|
2
|
+
|
|
3
|
+
This guide explains how to set up a `utopia` website for local development and deployment.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Utopia is built on Ruby and Rack. Therefore, Ruby (suggested 2.0+) should be installed and working. Then, to install `utopia` and all required dependencies, run:
|
|
8
|
+
|
|
9
|
+
~~~ bash
|
|
10
|
+
$ gem install utopia
|
|
11
|
+
~~~
|
|
12
|
+
|
|
13
|
+
### Atom Integration
|
|
14
|
+
|
|
15
|
+
Utopia uses [Trenni](https://github.com/ioquatix/trenni) for templates and it has a syntax slightly different from ERB. However, there is a [package for Atom](https://atom.io/packages/language-trenni) which provides accurate syntax highlighting.
|
|
16
|
+
|
|
17
|
+
## Your First Page
|
|
18
|
+
|
|
19
|
+
To setup the default site, create a directory (typically the hostname of the site you want to create) and use the `bake utopia:site:create` command:
|
|
20
|
+
|
|
21
|
+
~~~ bash
|
|
22
|
+
$ mkdir www.example.com
|
|
23
|
+
$ cd www.example.com
|
|
24
|
+
$ bake --gem utopia utopia:site:create
|
|
25
|
+
$ bake utopia:development
|
|
26
|
+
~~~
|
|
27
|
+
|
|
28
|
+
You will now have a basic template site running on `https://localhost:9292`.
|
|
29
|
+
|
|
30
|
+
### Welcome Page
|
|
31
|
+
|
|
32
|
+
Utopia includes a redirection middleware to redirect all root-level requests to a given URI. The default being `/welcome/index`:
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
# in config.ru
|
|
36
|
+
|
|
37
|
+
use Utopia::Redirection::Rewrite,
|
|
38
|
+
"/" => "/welcome/index"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The content for this page is stored in `pages/welcome/index.xnode`. The format of this page is a subset of HTML5 - open and close tags are strictly enforced.
|
|
42
|
+
|
|
43
|
+
There are several special tags which are used for creating modular content. The most common one is the outer `<content:page>` tag. Utopia uses the name `page` to lookup the file-system hierarchy. First, it looks for `/welcome/_page.xnode`, and then it looks for `/_page.xnode` which it finds. This page template includes a special `<utopia:content/>` tag which is replaced with the inner body of the `<content:page>` tag. This recursive lookup is the heart of Utopia.
|
|
44
|
+
|
|
45
|
+
### Links
|
|
46
|
+
|
|
47
|
+
Utopia is a content-centric web application platform. It leverages the file-system to provide a mapping between logical resources and files on disk. The primary mode of mapping incoming requests to specific nodes (content) is done using the `links.yaml` file.
|
|
48
|
+
|
|
49
|
+
The links file associates metadata with node names for a given directory. This can include things like redirects, titles, descriptions, etc. You can add any metadata you like, to support your specific use-case. The primary use of the links files is to provide site structure, e.g. menus. In addition, they can function as a rudimentary data-store for static information, e.g. a list of applications (each with it's own page), a list of features, etc.
|
|
50
|
+
|
|
51
|
+
You'll notice that there is a file `/links.yaml`. This file contains important metadata relating to the `errors` subdirectory. As we don't want these nodes showing up in a top level menu, we mark them as `display: false`
|
|
52
|
+
|
|
53
|
+
~~~ yaml
|
|
54
|
+
errors:
|
|
55
|
+
display: false
|
|
56
|
+
~~~
|
|
57
|
+
|
|
58
|
+
## Testing
|
|
59
|
+
|
|
60
|
+
Utopia websites include a default set of tests using `sus`. These specs can test against the actual running website.
|
|
61
|
+
|
|
62
|
+
~~~ bash
|
|
63
|
+
$ sus
|
|
64
|
+
|
|
65
|
+
1 samples: 1x 200. 3703.7 requests per second. S/D: 0.000µs.
|
|
66
|
+
1 passed out of 1 total (2 assertions)
|
|
67
|
+
🏁 Finished in 247.4ms; 8.085 assertions per second.
|
|
68
|
+
~~~
|
|
69
|
+
|
|
70
|
+
The website test will spider all pages on your site and report any broken links as failures.
|
|
71
|
+
|
|
72
|
+
### Coverage
|
|
73
|
+
|
|
74
|
+
The [covered](https://github.com/socketry/covered) gem is used for providing source code coverage information.
|
|
75
|
+
|
|
76
|
+
~~~ bash
|
|
77
|
+
$ COVERAGE=BriefSummary rspec
|
|
78
|
+
|
|
79
|
+
website
|
|
80
|
+
1 samples: 1x 200. 67.53 requests per second. S/D: 0.000µs.
|
|
81
|
+
should be responsive
|
|
82
|
+
|
|
83
|
+
* 5 files checked; 33/46 lines executed; 71.74% covered.
|
|
84
|
+
|
|
85
|
+
Least Coverage:
|
|
86
|
+
pages/_page.xnode: 6 lines not executed!
|
|
87
|
+
config.ru: 4 lines not executed!
|
|
88
|
+
pages/welcome/index.xnode: 2 lines not executed!
|
|
89
|
+
pages/_heading.xnode: 1 lines not executed!
|
|
90
|
+
|
|
91
|
+
Finished in 1.82 seconds (files took 0.51845 seconds to load)
|
|
92
|
+
1 example, 0 failures
|
|
93
|
+
~~~
|
data/context/index.yaml
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Automatically generated context index for Utopia::Project guides.
|
|
2
|
+
# Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
|
|
3
|
+
---
|
|
4
|
+
description: Utopia is a framework for building dynamic content-driven websites.
|
|
5
|
+
metadata:
|
|
6
|
+
documentation_uri: https://socketry.github.io/utopia/
|
|
7
|
+
funding_uri: https://github.com/sponsors/ioquatix/
|
|
8
|
+
source_code_uri: https://github.com/socketry/utopia.git
|
|
9
|
+
files:
|
|
10
|
+
- path: getting-started.md
|
|
11
|
+
title: Getting Started
|
|
12
|
+
description: This guide explains how to set up a `utopia` website for local development
|
|
13
|
+
and deployment.
|
|
14
|
+
- path: middleware.md
|
|
15
|
+
title: Middleware
|
|
16
|
+
description: This guide gives an overview of the different Rack middleware used
|
|
17
|
+
by Utopia.
|
|
18
|
+
- path: server-setup.md
|
|
19
|
+
title: Server Setup
|
|
20
|
+
description: This guide explains how to deploy a `utopia` web application.
|
|
21
|
+
- path: integrating-with-javascript.md
|
|
22
|
+
title: Installing JavaScript Libraries
|
|
23
|
+
description: Utopia integrates with Yarn and provides a [bake task](https://github.com/ioquatix/bake)
|
|
24
|
+
to simplify deployment packages distributed using `yarn` that implement the `dist`
|
|
25
|
+
sub-directory convention.
|
|
26
|
+
- path: what-is-xnode.md
|
|
27
|
+
title: What is XNode?
|
|
28
|
+
description: This guide explains the `xnode` view layer and how it can be used to
|
|
29
|
+
build efficient websites.
|
|
30
|
+
- path: updating-utopia.md
|
|
31
|
+
title: Updating Utopia
|
|
32
|
+
description: This guide explains how to update existing `utopia` websites.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Installing JavaScript Libraries
|
|
2
|
+
|
|
3
|
+
Utopia integrates with Yarn and provides a [bake task](https://github.com/ioquatix/bake) to simplify deployment packages distributed using `yarn` that implement the `dist` sub-directory convention.
|
|
4
|
+
|
|
5
|
+
## Installing Yarn
|
|
6
|
+
|
|
7
|
+
If you don't already have yarn installed, make sure you have npm installed and then run the following command:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
$ sudo npm install -g yarn
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Installing jQuery
|
|
14
|
+
|
|
15
|
+
Firstly, ensure your project has a `package.json` file:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
$ yarn init
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then install jquery using `yarn`:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
$ yarn add jquery
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Copy the distribution scripts to `public/_components`:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
$ bundle exec bake utopia:node:update
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then add the appropriate `<script>` tags to `pages/_page.xnode`:
|
|
34
|
+
|
|
35
|
+
```html
|
|
36
|
+
<script type="text/javascript" src="/_components/jquery/jquery.min.js"></script>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Using JavaScript
|
|
40
|
+
|
|
41
|
+
You can use JavaScript by embedding it directly into your HTML, or by creating a JavaScript source file and referencing that.
|
|
42
|
+
|
|
43
|
+
### Embedding Code
|
|
44
|
+
|
|
45
|
+
In your HTML view:
|
|
46
|
+
|
|
47
|
+
```trenni
|
|
48
|
+
<html>
|
|
49
|
+
<body>
|
|
50
|
+
<script type="text/javascript">
|
|
51
|
+
//<![CDATA[
|
|
52
|
+
console.log("Hello World")
|
|
53
|
+
//]]>
|
|
54
|
+
</script>
|
|
55
|
+
</body>
|
|
56
|
+
</html>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### External Script
|
|
60
|
+
|
|
61
|
+
In `script.js`:
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
console.log("Hello World")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
In your HTML view:
|
|
68
|
+
|
|
69
|
+
```trenni
|
|
70
|
+
<html>
|
|
71
|
+
<body>
|
|
72
|
+
<script type="text/javascript" src="script.js"></script>
|
|
73
|
+
</body>
|
|
74
|
+
</html>
|
|
75
|
+
```
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Middleware
|
|
2
|
+
|
|
3
|
+
This guide gives an overview of the different Rack middleware used by Utopia.
|
|
4
|
+
|
|
5
|
+
## Static
|
|
6
|
+
|
|
7
|
+
The {ruby Utopia::Static} middleware services static files efficiently. By default, it works with `Rack::Sendfile` and supports `ETag` based caching. Normally, you'd prefer to put static files into `public/_static` but it's also acceptable to put static content into `pages/` if it makes sense.
|
|
8
|
+
|
|
9
|
+
~~~ ruby
|
|
10
|
+
use Utopia::Static,
|
|
11
|
+
# The root path to serve files from:
|
|
12
|
+
root: "path/to/root",
|
|
13
|
+
# The mime-types to recognize/serve:
|
|
14
|
+
types: [:default, :xiph],
|
|
15
|
+
# Cache-Control header for files:
|
|
16
|
+
cache_control: 'public, max-age=7200'
|
|
17
|
+
~~~
|
|
18
|
+
|
|
19
|
+
## Redirection
|
|
20
|
+
|
|
21
|
+
The {ruby Utopia::Redirection} middleware is used for redirecting requests based on patterns and status codes.
|
|
22
|
+
|
|
23
|
+
~~~ ruby
|
|
24
|
+
# String (fast hash lookup) rewriting:
|
|
25
|
+
use Utopia::Redirection::Rewrite,
|
|
26
|
+
'/' => '/welcome/index'
|
|
27
|
+
|
|
28
|
+
# Redirect directories (e.g. /) to an index file (e.g. /index):
|
|
29
|
+
use Utopia::Redirection::DirectoryIndex,
|
|
30
|
+
index: 'index.html'
|
|
31
|
+
|
|
32
|
+
# Redirect (error) status codes to actual pages:
|
|
33
|
+
use Utopia::Redirection::Errors,
|
|
34
|
+
404 => '/errors/file-not-found'
|
|
35
|
+
~~~
|
|
36
|
+
|
|
37
|
+
## Localization
|
|
38
|
+
|
|
39
|
+
The {ruby Utopia::Localization} middleware provides non-intrusive localization on top of the controller and view layers. The middleware uses the `accept-language` header to guess the preferred locale out of the given options. If a request path maps to a resource, that resource is returned. Otherwise, a non-localized request is made.
|
|
40
|
+
|
|
41
|
+
~~~ ruby
|
|
42
|
+
use Utopia::Localization,
|
|
43
|
+
:default_locale => 'en',
|
|
44
|
+
:locales => ['en', 'de', 'ja', 'zh']
|
|
45
|
+
~~~
|
|
46
|
+
|
|
47
|
+
To localize a specific `xnode`, append the locale as a postfix:
|
|
48
|
+
|
|
49
|
+
~~~
|
|
50
|
+
pages/index.xnode
|
|
51
|
+
pages/index.de.xnode
|
|
52
|
+
pages/index.ja.xnode
|
|
53
|
+
pages/index.zh.xnode
|
|
54
|
+
~~~
|
|
55
|
+
|
|
56
|
+
You can also access the current locale in the view via {ruby Utopia::Content::Node::Context#localization}.
|
|
57
|
+
|
|
58
|
+
## Controller
|
|
59
|
+
|
|
60
|
+
The {ruby Utopia::Controller} middleware provides flexible nested controllers with efficient behaviour. Controllers are nested in the `pages` directory and are matched against the incoming request path recursively, from outer most to inner most.
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
use Utopia::Controller,
|
|
64
|
+
# The root directory where `controller.rb` files can be found.
|
|
65
|
+
root: "path/to/root",
|
|
66
|
+
# The base class to use for all controllers:
|
|
67
|
+
base: Utopia::Controller::Base
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
A controller is a file within the specified root directory (typically `pages`) with the name `controller.rb`. This code is dynamically loaded into an anonymous class and executed. The default controller has only a single function:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
def passthrough(request, path)
|
|
74
|
+
# Call one of:
|
|
75
|
+
|
|
76
|
+
# This will cause the middleware to generate a response.
|
|
77
|
+
# def respond!(response)
|
|
78
|
+
|
|
79
|
+
# This will cause the controller to skip the request.
|
|
80
|
+
# def ignore!
|
|
81
|
+
|
|
82
|
+
# Request relative redirect. Respond with a redirect to the given target.
|
|
83
|
+
# def redirect! (target, status = 302)
|
|
84
|
+
|
|
85
|
+
# Controller relative redirect.
|
|
86
|
+
# def goto!(target, status = 302)
|
|
87
|
+
|
|
88
|
+
# Respond with an error which indiciates some kind of failure.
|
|
89
|
+
# def fail!(error = 400, message = nil)
|
|
90
|
+
|
|
91
|
+
# Succeed the request and immediately respond.
|
|
92
|
+
# def succeed!(status: 200, headers: {}, **options)
|
|
93
|
+
# options may include content: string or body: Enumerable (as per Rack specifications
|
|
94
|
+
|
|
95
|
+
suceed!
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The controller layer can do more complex operations by prepending modules into it.
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
prepend Rewrite, Actions
|
|
103
|
+
|
|
104
|
+
# Extracts an Integer
|
|
105
|
+
rewrite.extract_prefix id: Integer do
|
|
106
|
+
@user = User.find_by_id(@id)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
on "edit" do |request, path|
|
|
110
|
+
if request.post?
|
|
111
|
+
@user.update_attributes(request[:user])
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
otherwise do |request, path|
|
|
116
|
+
# Executed if no specific named actions were executed.
|
|
117
|
+
succeed!
|
|
118
|
+
end
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The incoming path is relative to the path of the controller itself.
|
|
122
|
+
|
|
123
|
+
## Content
|
|
124
|
+
|
|
125
|
+
The {ruby Utopia::Content} middleware parses XML-style templates with using attributes provided by the controller layer. Dynamic tags can be used to build modular content.
|
|
126
|
+
|
|
127
|
+
~~~ ruby
|
|
128
|
+
use Utopia::Content
|
|
129
|
+
~~~
|
|
130
|
+
|
|
131
|
+
A basic template `create.xnode` looks something like:
|
|
132
|
+
|
|
133
|
+
~~~trenni
|
|
134
|
+
<content:page>
|
|
135
|
+
<content:heading>Create User</content:heading>
|
|
136
|
+
<form action="#">
|
|
137
|
+
<input name="name" />
|
|
138
|
+
<input type="submit" />
|
|
139
|
+
</form>
|
|
140
|
+
</content:page>
|
|
141
|
+
~~~
|
|
142
|
+
|
|
143
|
+
This template would typically be designed with supporting `_page.xnode` and `_heading.xnode` in the same directory or, more typically, somewhere further up the directory hierarchy.
|
|
144
|
+
|
|
145
|
+
## Session
|
|
146
|
+
|
|
147
|
+
The {ruby Utopia::Session} middleware provides session storage using encrypted client-side cookies. The session management uses symmetric private key encryption to store data on the client and avoid tampering.
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
use Utopia::Session,
|
|
151
|
+
expires_after: 3600 * 24,
|
|
152
|
+
# The private key is retried from the `environment.yaml` file:
|
|
153
|
+
secret: UTOPIA.secret_for(:session),
|
|
154
|
+
secure: true
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
All session data is stored on the client, but it's encrypted with a salt and the secret key. It is impossible for the client to decrypt the data without the secret stored on the server.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Server Setup
|
|
2
|
+
|
|
3
|
+
This guide explains how to deploy a `utopia` web application.
|
|
4
|
+
|
|
5
|
+
## Deployment
|
|
6
|
+
|
|
7
|
+
The preferred method of deployment to a production server is via git. The `utopia` command assists with setup of a remote git repository on the server. It will setup a `git` `post-update` hook which will deploy the site correctly and restart the application server for that site.
|
|
8
|
+
|
|
9
|
+
To setup a server for deployment:
|
|
10
|
+
|
|
11
|
+
~~~ bash
|
|
12
|
+
$ mkdir /srv/http/www.example.com
|
|
13
|
+
$ cd /srv/http/www.example.com
|
|
14
|
+
$ sudo -u http utopia server create
|
|
15
|
+
~~~
|
|
16
|
+
|
|
17
|
+
On your development machine, you should setup the git remote:
|
|
18
|
+
|
|
19
|
+
~~~ bash
|
|
20
|
+
$ git remote add production ssh://remote/srv/http/www.example.com
|
|
21
|
+
$ git push --set-upstream production main
|
|
22
|
+
~~~
|
|
23
|
+
|
|
24
|
+
### Default Environment
|
|
25
|
+
|
|
26
|
+
Utopia will load `config/environment.yaml` and update `ENV` before executing any code. By default, [variant](https://github.com/socketry/variant) is used for handling different environments. You can set default environment values using the `utopia` command:
|
|
27
|
+
|
|
28
|
+
~~~ bash
|
|
29
|
+
$ utopia environment VARIANT=staging DATABASE_VARIANT=staging1
|
|
30
|
+
ENV["VARIANT"] will default to "staging" unless otherwise specified.
|
|
31
|
+
ENV["DATABASE_VARIANT"] will default to "staging1" unless otherwise specified.
|
|
32
|
+
~~~
|
|
33
|
+
|
|
34
|
+
To set a value, write `KEY=VALUE`. To unset a key, write `KEY`.
|
|
35
|
+
|
|
36
|
+
When you run `bake` tasks or spawn a server, the values in `config/environment.yaml` will be the defaults. You can override them by manually specifying them, e.g. `DATABASE_ENV=development bake db:info`.
|
|
37
|
+
|
|
38
|
+
## Platform
|
|
39
|
+
|
|
40
|
+
The best deployment platform for Utopia is Linux, using [falcon](https://github.com/socketry/falcon).
|
|
41
|
+
|
|
42
|
+
### Sudo Setup
|
|
43
|
+
|
|
44
|
+
Create a file `/etc/sudoers.d/http` with the following contents:
|
|
45
|
+
|
|
46
|
+
```sudoers
|
|
47
|
+
# Allow user samuel to check out code as user http using git:
|
|
48
|
+
%wheel ALL=(http) NOPASSWD: ALL
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This allows the deploy task to correctly checkout code as user `http`.
|
|
52
|
+
|
|
53
|
+
## Automatic Deployment
|
|
54
|
+
|
|
55
|
+
Automatic deployment allows you to deploy updates to your site when they are committed to a specific branch.
|
|
56
|
+
|
|
57
|
+
### GitHub Actions
|
|
58
|
+
|
|
59
|
+
Here is a basic workflow, adapted from the [www.codeotaku.com workflow](https://github.com/ioquatix/www.codeotaku.com/blob/main/.github/workflows/development.yml).
|
|
60
|
+
|
|
61
|
+
~~~ yaml
|
|
62
|
+
name: Development
|
|
63
|
+
|
|
64
|
+
on: [push]
|
|
65
|
+
|
|
66
|
+
jobs:
|
|
67
|
+
test:
|
|
68
|
+
strategy:
|
|
69
|
+
matrix:
|
|
70
|
+
os:
|
|
71
|
+
- ubuntu
|
|
72
|
+
|
|
73
|
+
ruby:
|
|
74
|
+
- "3.2"
|
|
75
|
+
|
|
76
|
+
runs-on: ${{matrix.os}}-latest
|
|
77
|
+
|
|
78
|
+
steps:
|
|
79
|
+
- uses: actions/checkout@v1
|
|
80
|
+
- uses: ruby/setup-ruby@v1
|
|
81
|
+
with:
|
|
82
|
+
ruby-version: ${{matrix.ruby}}
|
|
83
|
+
- uses: actions/cache@v1
|
|
84
|
+
with:
|
|
85
|
+
path: vendor/bundle
|
|
86
|
+
key: ${{runner.os}}-${{matrix.ruby}}-${{hashFiles('**/Gemfile.lock')}}
|
|
87
|
+
restore-keys: |
|
|
88
|
+
${{runner.os}}-${{matrix.ruby}}-
|
|
89
|
+
- name: Bundle install
|
|
90
|
+
run: |
|
|
91
|
+
sudo apt-get install pkg-config
|
|
92
|
+
gem install bundler:2.1.4
|
|
93
|
+
bundle config path vendor/bundle
|
|
94
|
+
bundle install --jobs 4 --retry 3
|
|
95
|
+
- name: Run tests
|
|
96
|
+
run: ${{matrix.env}} bundle exec sus
|
|
97
|
+
|
|
98
|
+
deploy:
|
|
99
|
+
needs: test
|
|
100
|
+
runs-on: ubuntu-latest
|
|
101
|
+
if: github.ref == 'refs/heads/main'
|
|
102
|
+
|
|
103
|
+
steps:
|
|
104
|
+
- uses: actions/checkout@v1
|
|
105
|
+
- name: Push to remote system
|
|
106
|
+
env:
|
|
107
|
+
DEPLOY_KEY: ${{secrets.deploy_key}}
|
|
108
|
+
run: |
|
|
109
|
+
eval "$(ssh-agent -s)"
|
|
110
|
+
ssh-add - <<< $DEPLOY_KEY
|
|
111
|
+
mkdir ~/.ssh
|
|
112
|
+
ssh-keyscan -H www.oriontransfer.net >> ~/.ssh/known_hosts
|
|
113
|
+
git push -f ssh://http@www.oriontransfer.net/srv/http/www.codeotaku.com/ HEAD:main
|
|
114
|
+
~~~
|
|
115
|
+
|
|
116
|
+
You will need to add your own DEPLOY_KEY to the GitHub Secrets of your repository andupdate the hostnames and directories to suit your own setup.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Updating Utopia
|
|
2
|
+
|
|
3
|
+
This guide explains how to update existing `utopia` websites.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Utopia provides a model for both local development (`utopia:site:create`) and deployment (`utopia:server:create`). In addition, Utopia provides a basic upgrade path for existing sites when things within the framework change. These are not always automatic, so below are some recipes for how to update your site.
|
|
8
|
+
|
|
9
|
+
## Site Update
|
|
10
|
+
|
|
11
|
+
Utopia as a framework introduces changes and versions change according to semantic versioning.
|
|
12
|
+
|
|
13
|
+
### Controller Update 1.9.x to 2.x
|
|
14
|
+
|
|
15
|
+
The controller layer no longer automatically prepends the `Actions` layer. The following program does a best effort attempt to update existing controllers:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
#!/usr/bin/env ruby
|
|
19
|
+
|
|
20
|
+
paths = Dir.glob("**/controller.rb")
|
|
21
|
+
|
|
22
|
+
paths.each do |path|
|
|
23
|
+
lines = File.readlines(path)
|
|
24
|
+
|
|
25
|
+
prepend_line_index = lines.first(5).find_index{|line| line =~ /prepend/}
|
|
26
|
+
|
|
27
|
+
unless prepend_line_index
|
|
28
|
+
puts "Updating #{path}.."
|
|
29
|
+
File.open(path, "w") do |file|
|
|
30
|
+
file.puts "\nprepend Actions"
|
|
31
|
+
file.write lines.join
|
|
32
|
+
end
|
|
33
|
+
else
|
|
34
|
+
prepend_line = lines[prepend_line_index]
|
|
35
|
+
|
|
36
|
+
unless prepend_line =~ /Actions/
|
|
37
|
+
if lines.any?{|line| line =~ /on/}
|
|
38
|
+
lines[prepend_line_index] = "#{prepend_line.chomp}, Actions\n"
|
|
39
|
+
|
|
40
|
+
puts "Updating #{path}.."
|
|
41
|
+
File.open(path, "w") do |file|
|
|
42
|
+
file.write lines.join
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### View Update 1.9.x to 2.x
|
|
51
|
+
|
|
52
|
+
Dynamic tags in 2.x require namespaces. This affects all `.xnode` files, in particular the following 3 cases:
|
|
53
|
+
|
|
54
|
+
1. Rewrite `<(/?)(NAME)(\W)` to `<$1content:$2$3` where NAME is a tag which would expand using a `_NAME.xnode` file.
|
|
55
|
+
2. Rewrite `<content/>` to `<utopia:content/>`. This affects `<node>`, `<deferred>`, `<environment>` tags.
|
|
56
|
+
3. Rewrite `partial 'NAME'` to be `partial 'content:NAME'`.
|
|
57
|
+
|
|
58
|
+
## Server Update
|
|
59
|
+
|
|
60
|
+
The utopia server git hooks are updated occasionally to improve the deployment process or to handle changes in the underlying process.
|
|
61
|
+
|
|
62
|
+
You can run the update process on the server to bring the git hooks up to the latest version.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
$ cd /srv/http/website
|
|
66
|
+
$ utopia server update
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
You should keep your client and server deployment hooks in sync.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# What is XNode?
|
|
2
|
+
|
|
3
|
+
This guide explains the `xnode` view layer and how it can be used to build efficient websites.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`.xnode` is the file extension for files which are used to render HTML content. The extension is formed from `XML` document fragment which forms a `node` in a tree. These templates are designed to maximise the ratio of content to markup. They improve separation of concerns and semantic organisation because repeated markup can be reused easily.
|
|
8
|
+
|
|
9
|
+
Here is a example of a blog post:
|
|
10
|
+
|
|
11
|
+
~~~ xml
|
|
12
|
+
<content:entry title="My day as a fish">
|
|
13
|
+
<p>It was not very exciting</p>
|
|
14
|
+
</content:entry>
|
|
15
|
+
~~~
|
|
16
|
+
|
|
17
|
+
The {Utopia::Content} middleware is built on top of the [Trenni](https://github.com/ioquatix/trenni) template language which uses two-phase evaluation.
|
|
18
|
+
|
|
19
|
+
## Phase 1: Evaluation
|
|
20
|
+
|
|
21
|
+
Trenni processes the view content by evaluation `#{expressions}` and `<?r statements ?>`. This generates an output buffer. The output buffer should contain valid markup (i.e. balanced tags, no invalid characters).
|
|
22
|
+
|
|
23
|
+
## Phase 2: Markup
|
|
24
|
+
|
|
25
|
+
Once the template is evaluated to text, it is parsed again into an event stream which is used to generate the final output. The event stream contains things like "open tag", "attribute", "close tag", and so on, and these are fed into the `Utopia::Content` middleware which generates the actual content. Tags without namespaces are output verbatim, while tags with namespaces invoke the tag lookup machinery. This uses the tag name to invoke further behaviour, e.g. inserting more content. Here is a simple example of a basic page:
|
|
26
|
+
|
|
27
|
+
```xml
|
|
28
|
+
<content:page>
|
|
29
|
+
<content:heading>Welcome to my page</content:heading>
|
|
30
|
+
|
|
31
|
+
<p>This page is so awesome</p>
|
|
32
|
+
</content:page>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
In order to render this, you will need two additional files, `_page.xnode` and `_heading.xnode`. As a short example, `_heading.xnode` might look like this:
|
|
36
|
+
|
|
37
|
+
```xml
|
|
38
|
+
<h1><utopia:content/></h1>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
When the parser encounters `<content:heading>...` in the main page, it would evaluate the above template. `<utopia:content/>` is a special tag that evaluates to the content that the parent tag provided, so in this case: `"Welcome to my page"`. Thus, the final output is `<h1>Welcome to my page</h1>`.
|