elm-pages 2.1.11 → 3.0.0-beta.1
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.
- package/codegen/elm-pages-codegen.js +38507 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmi +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateData.elmo +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateDataTest.elmi +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-DeadCodeEliminateDataTest.elmo +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Reporter.elmi +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Reporter.elmo +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Runner.elmi +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Runner.elmo +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
- package/generator/{template/public/style.css → dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/lock} +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm.json +1 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +6795 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Runner.elm.js +25651 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_runner.js +110 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_supervisor.js +187 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/package.json +1 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/src/Reporter.elm +26 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/src/Runner.elm +62 -0
- package/generator/dead-code-review/elm.json +35 -0
- package/generator/dead-code-review/src/Pages/Review/DeadCodeEliminateData.elm +181 -0
- package/generator/dead-code-review/src/ReviewConfig.elm +9 -0
- package/generator/dead-code-review/tests/Pages/Review/DeadCodeEliminateDataTest.elm +455 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Internal-RoutePattern.elmi +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Internal-RoutePattern.elmo +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolations.elmi +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolations.elmo +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolationsTest.elmi +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Pages-Review-NoContractViolationsTest.elmo +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Reporter.elmi +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Reporter.elmo +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Runner.elmi +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/Runner.elmo +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/i.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/lock +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm.json +1 -0
- package/generator/review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +6795 -0
- package/generator/review/elm-stuff/tests-0.19.1/js/Runner.elm.js +27617 -0
- package/generator/review/elm-stuff/tests-0.19.1/js/node_runner.js +110 -0
- package/generator/review/elm-stuff/tests-0.19.1/js/node_supervisor.js +187 -0
- package/generator/review/elm-stuff/tests-0.19.1/js/package.json +1 -0
- package/generator/review/elm-stuff/tests-0.19.1/src/Reporter.elm +26 -0
- package/generator/review/elm-stuff/tests-0.19.1/src/Runner.elm +62 -0
- package/generator/review/elm.json +13 -4
- package/{src → generator/review/src}/Pages/Review/NoContractViolations.elm +148 -148
- package/generator/review/tests/Pages/Review/NoContractViolationsTest.elm +331 -0
- package/generator/src/RouteBuilder.elm +420 -0
- package/generator/src/SharedTemplate.elm +4 -5
- package/generator/src/SiteConfig.elm +3 -9
- package/generator/src/build.js +308 -95
- package/generator/src/cli.js +103 -8
- package/generator/src/codegen.js +192 -35
- package/generator/src/compile-elm.js +183 -31
- package/generator/src/dev-server.js +353 -96
- package/generator/src/elm-application.json +3 -1
- package/generator/src/elm-codegen.js +34 -0
- package/generator/src/elm-file-constants.js +2 -0
- package/generator/src/error-formatter.js +20 -1
- package/generator/src/generate-template-module-connector.js +125 -927
- package/generator/src/hello.ts +5 -0
- package/generator/src/pre-render-html.js +58 -104
- package/generator/src/render-worker.js +27 -13
- package/generator/src/render.js +252 -197
- package/generator/src/request-cache-fs.js +18 -0
- package/generator/src/request-cache.js +128 -56
- package/generator/src/rewrite-client-elm-json.js +49 -0
- package/generator/src/route-codegen-helpers.js +62 -1
- package/generator/static-code/dev-style.css +22 -0
- package/generator/static-code/elm-pages.js +43 -39
- package/generator/static-code/hmr.js +98 -88
- package/generator/template/app/Api.elm +25 -0
- package/generator/template/app/ErrorPage.elm +38 -0
- package/generator/template/app/Route/Index.elm +87 -0
- package/generator/template/{src → app}/Shared.elm +34 -13
- package/generator/template/app/Site.elm +19 -0
- package/generator/template/{src → app}/View.elm +0 -0
- package/generator/template/elm-pages.config.mjs +5 -0
- package/generator/template/elm.json +1 -0
- package/generator/template/{public/index.js → index.ts} +7 -3
- package/generator/template/package.json +4 -4
- package/generator/template/public/favicon.ico +0 -0
- package/generator/template/public/images/icon-png.png +0 -0
- package/generator/template/src/.gitkeep +0 -0
- package/generator/template/style.css +4 -0
- package/package.json +33 -23
- package/src/ApiRoute.elm +176 -43
- package/src/BuildError.elm +10 -1
- package/src/CookieParser.elm +84 -0
- package/src/DataSource/Env.elm +38 -0
- package/src/DataSource/File.elm +27 -16
- package/src/DataSource/Glob.elm +126 -80
- package/src/DataSource/Http.elm +283 -304
- package/src/DataSource/Internal/Glob.elm +5 -21
- package/src/DataSource/Internal/Request.elm +25 -0
- package/src/DataSource/Port.elm +17 -14
- package/src/DataSource.elm +55 -318
- package/src/Form/Field.elm +717 -0
- package/src/Form/FieldStatus.elm +36 -0
- package/src/Form/FieldView.elm +417 -0
- package/src/Form/FormData.elm +22 -0
- package/src/Form/Validation.elm +391 -0
- package/src/Form/Value.elm +118 -0
- package/src/Form.elm +1683 -0
- package/src/FormData.elm +58 -0
- package/src/FormDecoder.elm +102 -0
- package/src/Head/Seo.elm +12 -4
- package/src/Head.elm +12 -2
- package/src/HtmlPrinter.elm +1 -1
- package/src/Internal/ApiRoute.elm +17 -4
- package/src/Internal/Request.elm +7 -0
- package/src/PageServerResponse.elm +68 -0
- package/src/Pages/ContentCache.elm +1 -229
- package/src/Pages/Fetcher.elm +58 -0
- package/src/Pages/FormState.elm +256 -0
- package/src/Pages/Generate.elm +800 -0
- package/src/Pages/Internal/Form.elm +17 -0
- package/src/Pages/Internal/NotFoundReason.elm +3 -55
- package/src/Pages/Internal/Platform/Cli.elm +777 -579
- package/src/Pages/Internal/Platform/Effect.elm +5 -5
- package/src/Pages/Internal/Platform/StaticResponses.elm +178 -394
- package/src/Pages/Internal/Platform/ToJsPayload.elm +24 -23
- package/src/Pages/Internal/Platform.elm +1244 -504
- package/src/Pages/Internal/ResponseSketch.elm +19 -0
- package/src/Pages/Internal/RoutePattern.elm +596 -45
- package/src/Pages/Manifest.elm +26 -0
- package/src/Pages/Msg.elm +79 -0
- package/src/Pages/ProgramConfig.elm +67 -14
- package/src/Pages/SiteConfig.elm +3 -6
- package/src/Pages/StaticHttp/Request.elm +4 -2
- package/src/Pages/StaticHttpRequest.elm +50 -215
- package/src/Pages/Transition.elm +70 -0
- package/src/Path.elm +1 -0
- package/src/Pattern.elm +98 -0
- package/src/RenderRequest.elm +2 -2
- package/src/RequestsAndPending.elm +111 -9
- package/src/Server/Request.elm +1253 -0
- package/src/Server/Response.elm +292 -0
- package/src/Server/Session.elm +316 -0
- package/src/Server/SetCookie.elm +169 -0
- package/src/TerminalText.elm +1 -1
- package/src/Test/Html/Internal/ElmHtml/Markdown.elm +0 -1
- package/src/Test/Html/Internal/ElmHtml/ToString.elm +1 -1
- package/generator/src/Page.elm +0 -359
- package/generator/src/codegen-template-module.js +0 -183
- package/generator/src/elm-pages-js-minified.js +0 -1
- package/generator/template/src/Api.elm +0 -14
- package/generator/template/src/Page/Index.elm +0 -69
- package/generator/template/src/Site.elm +0 -41
- package/src/DataSource/ServerRequest.elm +0 -60
- package/src/Internal/OptimizedDecoder.elm +0 -18
- package/src/KeepOrDiscard.elm +0 -6
- package/src/OptimizedDecoder/Pipeline.elm +0 -335
- package/src/OptimizedDecoder.elm +0 -818
- package/src/Pages/Internal/ApplicationType.elm +0 -6
- package/src/Pages/Secrets.elm +0 -83
- package/src/Secrets.elm +0 -111
- package/src/SecretsDict.elm +0 -45
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
module Server.Response exposing
|
|
2
|
+
( Response
|
|
3
|
+
, json, plainText, temporaryRedirect, permanentRedirect
|
|
4
|
+
, emptyBody, body, bytesBody, base64Body
|
|
5
|
+
, render
|
|
6
|
+
, errorPage, mapError
|
|
7
|
+
, map
|
|
8
|
+
, withHeader, withHeaders, withStatusCode, withSetCookieHeader
|
|
9
|
+
, toJson
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
{-|
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## Responses
|
|
16
|
+
|
|
17
|
+
@docs Response
|
|
18
|
+
|
|
19
|
+
There are two top-level response types:
|
|
20
|
+
|
|
21
|
+
1. Server Responses
|
|
22
|
+
2. Render Responses
|
|
23
|
+
|
|
24
|
+
A Server Response is a way to directly send a low-level server response, with no additional magic. You can set a String body,
|
|
25
|
+
a list of headers, the status code, etc. The Server Response helpers like `json` and `temporaryRedirect` are just helpers for
|
|
26
|
+
building up those low-level Server Responses.
|
|
27
|
+
|
|
28
|
+
Render Responses are a little more special in the way they are connected to your elm-pages app. They allow you to render
|
|
29
|
+
the current Route Module. To do that, you'll need to pass along the `data` for your Route Module.
|
|
30
|
+
|
|
31
|
+
You can use `withHeader` and `withStatusCode` to customize either type of Response (Server Responses or Render Responses).
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
## Server Responses
|
|
35
|
+
|
|
36
|
+
@docs json, plainText, temporaryRedirect, permanentRedirect
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## Custom Responses
|
|
40
|
+
|
|
41
|
+
@docs emptyBody, body, bytesBody, base64Body
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
## Render Responses
|
|
45
|
+
|
|
46
|
+
@docs render
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Rendering Error Pages
|
|
50
|
+
|
|
51
|
+
@docs errorPage, mapError
|
|
52
|
+
|
|
53
|
+
@docs map
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
## Amending Responses
|
|
57
|
+
|
|
58
|
+
@docs withHeader, withHeaders, withStatusCode, withSetCookieHeader
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
## Internals
|
|
62
|
+
|
|
63
|
+
@docs toJson
|
|
64
|
+
|
|
65
|
+
-}
|
|
66
|
+
|
|
67
|
+
import Base64
|
|
68
|
+
import Bytes exposing (Bytes)
|
|
69
|
+
import Json.Encode
|
|
70
|
+
import PageServerResponse exposing (PageServerResponse(..))
|
|
71
|
+
import Server.SetCookie as SetCookie exposing (SetCookie)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
{-| -}
|
|
75
|
+
type alias Response data error =
|
|
76
|
+
PageServerResponse data error
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
{-| -}
|
|
80
|
+
map : (data -> mappedData) -> Response data error -> Response mappedData error
|
|
81
|
+
map mapFn pageServerResponse =
|
|
82
|
+
case pageServerResponse of
|
|
83
|
+
RenderPage response data ->
|
|
84
|
+
RenderPage response (mapFn data)
|
|
85
|
+
|
|
86
|
+
ServerResponse serverResponse ->
|
|
87
|
+
ServerResponse serverResponse
|
|
88
|
+
|
|
89
|
+
ErrorPage error response ->
|
|
90
|
+
ErrorPage error response
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
{-| -}
|
|
94
|
+
mapError : (errorPage -> mappedErrorPage) -> Response data errorPage -> Response data mappedErrorPage
|
|
95
|
+
mapError mapFn pageServerResponse =
|
|
96
|
+
case pageServerResponse of
|
|
97
|
+
RenderPage response data ->
|
|
98
|
+
RenderPage response data
|
|
99
|
+
|
|
100
|
+
ServerResponse serverResponse ->
|
|
101
|
+
ServerResponse serverResponse
|
|
102
|
+
|
|
103
|
+
ErrorPage error response ->
|
|
104
|
+
ErrorPage (mapFn error) response
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
{-| -}
|
|
108
|
+
plainText : String -> Response data error
|
|
109
|
+
plainText string =
|
|
110
|
+
{ statusCode = 200
|
|
111
|
+
, headers = [ ( "Content-Type", "text/plain" ) ]
|
|
112
|
+
, body = Just string
|
|
113
|
+
, isBase64Encoded = False
|
|
114
|
+
}
|
|
115
|
+
|> ServerResponse
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
{-| -}
|
|
119
|
+
render : data -> Response data error
|
|
120
|
+
render data =
|
|
121
|
+
RenderPage
|
|
122
|
+
{ statusCode = 200, headers = [] }
|
|
123
|
+
data
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
{-| -}
|
|
127
|
+
errorPage : errorPage -> Response data errorPage
|
|
128
|
+
errorPage errorPage_ =
|
|
129
|
+
ErrorPage errorPage_ { headers = [] }
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
{-| -}
|
|
133
|
+
emptyBody : Response data error
|
|
134
|
+
emptyBody =
|
|
135
|
+
{ statusCode = 200
|
|
136
|
+
, headers = []
|
|
137
|
+
, body = Nothing
|
|
138
|
+
, isBase64Encoded = False
|
|
139
|
+
}
|
|
140
|
+
|> ServerResponse
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
{-| -}
|
|
144
|
+
body : String -> Response data error
|
|
145
|
+
body body_ =
|
|
146
|
+
{ statusCode = 200
|
|
147
|
+
, headers = []
|
|
148
|
+
, body = Just body_
|
|
149
|
+
, isBase64Encoded = False
|
|
150
|
+
}
|
|
151
|
+
|> ServerResponse
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
{-| -}
|
|
155
|
+
base64Body : String -> Response data error
|
|
156
|
+
base64Body base64String =
|
|
157
|
+
{ statusCode = 200
|
|
158
|
+
, headers = []
|
|
159
|
+
, body = Just base64String
|
|
160
|
+
, isBase64Encoded = True
|
|
161
|
+
}
|
|
162
|
+
|> ServerResponse
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
{-| -}
|
|
166
|
+
bytesBody : Bytes -> Response data error
|
|
167
|
+
bytesBody bytes =
|
|
168
|
+
{ statusCode = 200
|
|
169
|
+
, headers = []
|
|
170
|
+
, body = bytes |> Base64.fromBytes
|
|
171
|
+
, isBase64Encoded = True
|
|
172
|
+
}
|
|
173
|
+
|> ServerResponse
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
{-| -}
|
|
177
|
+
json : Json.Encode.Value -> Response data error
|
|
178
|
+
json jsonValue =
|
|
179
|
+
{ statusCode = 200
|
|
180
|
+
, headers =
|
|
181
|
+
[ ( "Content-Type", "application/json" )
|
|
182
|
+
]
|
|
183
|
+
, body =
|
|
184
|
+
jsonValue
|
|
185
|
+
|> Json.Encode.encode 0
|
|
186
|
+
|> Just
|
|
187
|
+
, isBase64Encoded = False
|
|
188
|
+
}
|
|
189
|
+
|> ServerResponse
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
{-| Build a 308 permanent redirect response.
|
|
193
|
+
|
|
194
|
+
Permanent redirects tell the browser that a resource has permanently moved. If you redirect because a user is not logged in,
|
|
195
|
+
then you **do not** want to use a permanent redirect because the page they are looking for hasn't changed, you are just
|
|
196
|
+
temporarily pointing them to a new page since they need to authenticate.
|
|
197
|
+
|
|
198
|
+
Permanent redirects are aggressively cached so be careful not to use them when you mean to use temporary redirects instead.
|
|
199
|
+
|
|
200
|
+
If you need to specifically rely on a 301 permanent redirect (see <https://stackoverflow.com/a/42138726> on the difference between 301 and 308),
|
|
201
|
+
use `customResponse` instead.
|
|
202
|
+
|
|
203
|
+
-}
|
|
204
|
+
permanentRedirect : String -> Response data error
|
|
205
|
+
permanentRedirect url =
|
|
206
|
+
{ body = Nothing
|
|
207
|
+
, statusCode = 308
|
|
208
|
+
, headers =
|
|
209
|
+
[ ( "Location", url )
|
|
210
|
+
]
|
|
211
|
+
, isBase64Encoded = False
|
|
212
|
+
}
|
|
213
|
+
|> ServerResponse
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
{-| -}
|
|
217
|
+
temporaryRedirect : String -> Response data error
|
|
218
|
+
temporaryRedirect url =
|
|
219
|
+
{ body = Nothing
|
|
220
|
+
, statusCode = 302
|
|
221
|
+
, headers =
|
|
222
|
+
[ ( "Location", url )
|
|
223
|
+
]
|
|
224
|
+
, isBase64Encoded = False
|
|
225
|
+
}
|
|
226
|
+
|> ServerResponse
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
{-| -}
|
|
230
|
+
withStatusCode : Int -> Response data Never -> Response data Never
|
|
231
|
+
withStatusCode statusCode serverResponse =
|
|
232
|
+
case serverResponse of
|
|
233
|
+
RenderPage response data ->
|
|
234
|
+
RenderPage { response | statusCode = statusCode } data
|
|
235
|
+
|
|
236
|
+
ServerResponse response ->
|
|
237
|
+
ServerResponse { response | statusCode = statusCode }
|
|
238
|
+
|
|
239
|
+
ErrorPage error _ ->
|
|
240
|
+
never error
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
{-| -}
|
|
244
|
+
withHeader : String -> String -> Response data error -> Response data error
|
|
245
|
+
withHeader name value serverResponse =
|
|
246
|
+
case serverResponse of
|
|
247
|
+
RenderPage response data ->
|
|
248
|
+
RenderPage { response | headers = ( name, value ) :: response.headers } data
|
|
249
|
+
|
|
250
|
+
ServerResponse response ->
|
|
251
|
+
ServerResponse { response | headers = ( name, value ) :: response.headers }
|
|
252
|
+
|
|
253
|
+
ErrorPage error response ->
|
|
254
|
+
ErrorPage error { response | headers = ( name, value ) :: response.headers }
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
{-| -}
|
|
258
|
+
withHeaders : List ( String, String ) -> Response data error -> Response data error
|
|
259
|
+
withHeaders headers serverResponse =
|
|
260
|
+
case serverResponse of
|
|
261
|
+
RenderPage response data ->
|
|
262
|
+
RenderPage { response | headers = headers ++ response.headers } data
|
|
263
|
+
|
|
264
|
+
ServerResponse response ->
|
|
265
|
+
ServerResponse { response | headers = headers ++ response.headers }
|
|
266
|
+
|
|
267
|
+
ErrorPage error response ->
|
|
268
|
+
ErrorPage error { response | headers = headers ++ response.headers }
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
{-| -}
|
|
272
|
+
withSetCookieHeader : SetCookie -> Response data error -> Response data error
|
|
273
|
+
withSetCookieHeader cookie response =
|
|
274
|
+
response
|
|
275
|
+
|> withHeader "Set-Cookie"
|
|
276
|
+
(cookie
|
|
277
|
+
|> SetCookie.toString
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
{-| -}
|
|
282
|
+
toJson : Response Never Never -> Json.Encode.Value
|
|
283
|
+
toJson response =
|
|
284
|
+
case response of
|
|
285
|
+
RenderPage _ data ->
|
|
286
|
+
never data
|
|
287
|
+
|
|
288
|
+
ServerResponse serverResponse ->
|
|
289
|
+
PageServerResponse.toJson serverResponse
|
|
290
|
+
|
|
291
|
+
ErrorPage error _ ->
|
|
292
|
+
never error
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
module Server.Session exposing (Decoder, NotLoadedReason(..), Session(..), Value(..), clearFlashCookies, empty, expectSession, flashPrefix, get, insert, remove, setValues, succeed, unwrap, update, withFlash, withSession)
|
|
2
|
+
|
|
3
|
+
{-|
|
|
4
|
+
|
|
5
|
+
@docs Decoder, NotLoadedReason, Session, Value, clearFlashCookies, empty, expectSession, flashPrefix, get, insert, remove, setValues, succeed, unwrap, update, withFlash, withSession
|
|
6
|
+
|
|
7
|
+
-}
|
|
8
|
+
|
|
9
|
+
import DataSource exposing (DataSource)
|
|
10
|
+
import DataSource.Http
|
|
11
|
+
import DataSource.Internal.Request
|
|
12
|
+
import Dict exposing (Dict)
|
|
13
|
+
import Dict.Extra
|
|
14
|
+
import Json.Decode
|
|
15
|
+
import Json.Encode
|
|
16
|
+
import Server.Request as Request exposing (Parser)
|
|
17
|
+
import Server.Response exposing (Response)
|
|
18
|
+
import Server.SetCookie as SetCookie
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
{-| -}
|
|
22
|
+
type Session
|
|
23
|
+
= Session (Dict String Value)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
{-| -}
|
|
27
|
+
type alias Decoder decoded =
|
|
28
|
+
Json.Decode.Decoder decoded
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
{-| -}
|
|
32
|
+
type Value
|
|
33
|
+
= Persistent String
|
|
34
|
+
| ExpiringFlash String
|
|
35
|
+
| NewFlash String
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
{-| -}
|
|
39
|
+
withFlash : String -> String -> Session -> Session
|
|
40
|
+
withFlash key value (Session session) =
|
|
41
|
+
session
|
|
42
|
+
|> Dict.insert key (NewFlash value)
|
|
43
|
+
|> Session
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
{-| -}
|
|
47
|
+
insert : String -> String -> Session -> Session
|
|
48
|
+
insert key value (Session session) =
|
|
49
|
+
session
|
|
50
|
+
|> Dict.insert key (Persistent value)
|
|
51
|
+
|> Session
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
{-| -}
|
|
55
|
+
get : String -> Session -> Maybe String
|
|
56
|
+
get key (Session session) =
|
|
57
|
+
session
|
|
58
|
+
|> Dict.get key
|
|
59
|
+
|> Maybe.map unwrap
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
{-| -}
|
|
63
|
+
unwrap : Value -> String
|
|
64
|
+
unwrap value =
|
|
65
|
+
case value of
|
|
66
|
+
Persistent string ->
|
|
67
|
+
string
|
|
68
|
+
|
|
69
|
+
ExpiringFlash string ->
|
|
70
|
+
string
|
|
71
|
+
|
|
72
|
+
NewFlash string ->
|
|
73
|
+
string
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
{-| -}
|
|
77
|
+
update : String -> (Maybe String -> Maybe String) -> Session -> Session
|
|
78
|
+
update key updateFn (Session session) =
|
|
79
|
+
session
|
|
80
|
+
|> Dict.update key
|
|
81
|
+
(\maybeValue ->
|
|
82
|
+
case maybeValue of
|
|
83
|
+
Just (Persistent value) ->
|
|
84
|
+
updateFn (Just value) |> Maybe.map Persistent
|
|
85
|
+
|
|
86
|
+
Just (ExpiringFlash value) ->
|
|
87
|
+
updateFn (Just value) |> Maybe.map NewFlash
|
|
88
|
+
|
|
89
|
+
Just (NewFlash value) ->
|
|
90
|
+
updateFn (Just value) |> Maybe.map NewFlash
|
|
91
|
+
|
|
92
|
+
Nothing ->
|
|
93
|
+
Nothing
|
|
94
|
+
|> updateFn
|
|
95
|
+
|> Maybe.map Persistent
|
|
96
|
+
)
|
|
97
|
+
|> Session
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
{-| -}
|
|
101
|
+
remove : String -> Session -> Session
|
|
102
|
+
remove key (Session session) =
|
|
103
|
+
session
|
|
104
|
+
|> Dict.remove key
|
|
105
|
+
|> Session
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
{-| -}
|
|
109
|
+
empty : Session
|
|
110
|
+
empty =
|
|
111
|
+
Session Dict.empty
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
{-| -}
|
|
115
|
+
type NotLoadedReason
|
|
116
|
+
= NoCookies
|
|
117
|
+
| MissingHeaders
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
{-| -}
|
|
121
|
+
succeed : constructor -> Decoder constructor
|
|
122
|
+
succeed constructor =
|
|
123
|
+
constructor
|
|
124
|
+
|> Json.Decode.succeed
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
{-| -}
|
|
128
|
+
setValues : Session -> Json.Encode.Value
|
|
129
|
+
setValues (Session session) =
|
|
130
|
+
session
|
|
131
|
+
|> Dict.toList
|
|
132
|
+
|> List.filterMap
|
|
133
|
+
(\( key, value ) ->
|
|
134
|
+
case value of
|
|
135
|
+
Persistent string ->
|
|
136
|
+
Just ( key, string )
|
|
137
|
+
|
|
138
|
+
NewFlash string ->
|
|
139
|
+
Just ( flashPrefix ++ key, string )
|
|
140
|
+
|
|
141
|
+
ExpiringFlash _ ->
|
|
142
|
+
Nothing
|
|
143
|
+
)
|
|
144
|
+
|> List.map (Tuple.mapSecond Json.Encode.string)
|
|
145
|
+
|> Json.Encode.object
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
{-| -}
|
|
149
|
+
flashPrefix : String
|
|
150
|
+
flashPrefix =
|
|
151
|
+
"__flash__"
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
{-| -}
|
|
155
|
+
clearFlashCookies : Dict String String -> Dict String String
|
|
156
|
+
clearFlashCookies dict =
|
|
157
|
+
Dict.Extra.removeWhen
|
|
158
|
+
(\key _ ->
|
|
159
|
+
key |> String.startsWith flashPrefix
|
|
160
|
+
)
|
|
161
|
+
dict
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
{-| -}
|
|
165
|
+
expectSession :
|
|
166
|
+
{ name : String
|
|
167
|
+
, secrets : DataSource (List String)
|
|
168
|
+
, sameSite : String
|
|
169
|
+
}
|
|
170
|
+
-> Parser request
|
|
171
|
+
-> (request -> Result () Session -> DataSource ( Session, Response data errorPage ))
|
|
172
|
+
-> Parser (DataSource (Response data errorPage))
|
|
173
|
+
expectSession config userRequest toRequest =
|
|
174
|
+
Request.map2
|
|
175
|
+
(\sessionCookie userRequestData ->
|
|
176
|
+
sessionCookie
|
|
177
|
+
|> decryptCookie config
|
|
178
|
+
|> DataSource.andThen
|
|
179
|
+
(encodeSessionUpdate config toRequest userRequestData)
|
|
180
|
+
)
|
|
181
|
+
(Request.expectCookie config.name)
|
|
182
|
+
userRequest
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
{-| -}
|
|
186
|
+
withSession :
|
|
187
|
+
{ name : String
|
|
188
|
+
, secrets : DataSource (List String)
|
|
189
|
+
, sameSite : String
|
|
190
|
+
}
|
|
191
|
+
-> Parser request
|
|
192
|
+
-> (request -> Result () (Maybe Session) -> DataSource ( Session, Response data errorPage ))
|
|
193
|
+
-> Parser (DataSource (Response data errorPage))
|
|
194
|
+
withSession config userRequest toRequest =
|
|
195
|
+
Request.map2
|
|
196
|
+
(\maybeSessionCookie userRequestData ->
|
|
197
|
+
let
|
|
198
|
+
decrypted : DataSource (Result () (Maybe Session))
|
|
199
|
+
decrypted =
|
|
200
|
+
case maybeSessionCookie of
|
|
201
|
+
Just sessionCookie ->
|
|
202
|
+
sessionCookie
|
|
203
|
+
|> decryptCookie config
|
|
204
|
+
|> DataSource.map (Result.map Just)
|
|
205
|
+
|
|
206
|
+
Nothing ->
|
|
207
|
+
Ok Nothing
|
|
208
|
+
|> DataSource.succeed
|
|
209
|
+
in
|
|
210
|
+
decrypted
|
|
211
|
+
|> DataSource.andThen
|
|
212
|
+
(encodeSessionUpdate config toRequest userRequestData)
|
|
213
|
+
)
|
|
214
|
+
(Request.cookie config.name)
|
|
215
|
+
userRequest
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
encodeSessionUpdate : { a | name : String, secrets : DataSource (List String) } -> (c -> d -> DataSource ( Session, Response data errorPage )) -> c -> d -> DataSource (Response data errorPage)
|
|
219
|
+
encodeSessionUpdate config toRequest userRequestData sessionResult =
|
|
220
|
+
sessionResult
|
|
221
|
+
|> toRequest userRequestData
|
|
222
|
+
|> DataSource.andThen
|
|
223
|
+
(\( sessionUpdate, response ) ->
|
|
224
|
+
DataSource.map
|
|
225
|
+
(\encoded ->
|
|
226
|
+
response
|
|
227
|
+
|> Server.Response.withSetCookieHeader
|
|
228
|
+
(SetCookie.setCookie config.name encoded
|
|
229
|
+
|> SetCookie.httpOnly
|
|
230
|
+
|> SetCookie.withPath "/"
|
|
231
|
+
-- TODO set expiration time
|
|
232
|
+
-- TODO do I need to encrypt the session expiration as part of it
|
|
233
|
+
-- TODO should I update the expiration time every time?
|
|
234
|
+
--|> SetCookie.withExpiration (Time.millisToPosix 100000000000)
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
(encrypt config.secrets
|
|
238
|
+
(setValues sessionUpdate)
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
decryptCookie : { a | secrets : DataSource (List String) } -> String -> DataSource (Result () Session)
|
|
244
|
+
decryptCookie config sessionCookie =
|
|
245
|
+
sessionCookie
|
|
246
|
+
|> decrypt config.secrets (Json.Decode.dict Json.Decode.string)
|
|
247
|
+
|> DataSource.map
|
|
248
|
+
(Result.map
|
|
249
|
+
(\dict ->
|
|
250
|
+
dict
|
|
251
|
+
|> Dict.toList
|
|
252
|
+
|> List.map
|
|
253
|
+
(\( key, value ) ->
|
|
254
|
+
if key |> String.startsWith flashPrefix then
|
|
255
|
+
( key |> String.dropLeft (flashPrefix |> String.length)
|
|
256
|
+
, ExpiringFlash value
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
else
|
|
260
|
+
( key, Persistent value )
|
|
261
|
+
)
|
|
262
|
+
|> Dict.fromList
|
|
263
|
+
|> Session
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
encrypt : DataSource (List String) -> Json.Encode.Value -> DataSource String
|
|
269
|
+
encrypt getSecrets input =
|
|
270
|
+
getSecrets
|
|
271
|
+
|> DataSource.andThen
|
|
272
|
+
(\secrets ->
|
|
273
|
+
DataSource.Internal.Request.request
|
|
274
|
+
{ name = "encrypt"
|
|
275
|
+
, body =
|
|
276
|
+
DataSource.Http.jsonBody
|
|
277
|
+
(Json.Encode.object
|
|
278
|
+
[ ( "values", input )
|
|
279
|
+
, ( "secret"
|
|
280
|
+
, Json.Encode.string
|
|
281
|
+
(secrets
|
|
282
|
+
|> List.head
|
|
283
|
+
-- TODO use different default - require non-empty list?
|
|
284
|
+
|> Maybe.withDefault ""
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
]
|
|
288
|
+
)
|
|
289
|
+
, expect =
|
|
290
|
+
DataSource.Http.expectJson
|
|
291
|
+
Json.Decode.string
|
|
292
|
+
}
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
decrypt : DataSource (List String) -> Json.Decode.Decoder a -> String -> DataSource (Result () a)
|
|
297
|
+
decrypt getSecrets decoder input =
|
|
298
|
+
getSecrets
|
|
299
|
+
|> DataSource.andThen
|
|
300
|
+
(\secrets ->
|
|
301
|
+
DataSource.Internal.Request.request
|
|
302
|
+
{ name = "decrypt"
|
|
303
|
+
, body =
|
|
304
|
+
DataSource.Http.jsonBody
|
|
305
|
+
(Json.Encode.object
|
|
306
|
+
[ ( "input", Json.Encode.string input )
|
|
307
|
+
, ( "secrets", Json.Encode.list Json.Encode.string secrets )
|
|
308
|
+
]
|
|
309
|
+
)
|
|
310
|
+
, expect =
|
|
311
|
+
decoder
|
|
312
|
+
|> Json.Decode.nullable
|
|
313
|
+
|> Json.Decode.map (Result.fromMaybe ())
|
|
314
|
+
|> DataSource.Http.expectJson
|
|
315
|
+
}
|
|
316
|
+
)
|