mirror of
https://github.com/dillonkearns/elm-pages-v3-beta.git
synced 2024-11-30 03:11:30 +03:00
Update blog example.
This commit is contained in:
parent
219e4b3a12
commit
48b92722e5
@ -196,6 +196,11 @@ action routeParams =
|
||||
[ ( "slug", Encode.string okForm.slug )
|
||||
, ( "title", Encode.string okForm.title )
|
||||
, ( "body", Encode.string okForm.body )
|
||||
, ( "publish"
|
||||
, okForm.publish
|
||||
|> Maybe.map (Date.toIsoString >> Encode.string)
|
||||
|> Maybe.withDefault Encode.null
|
||||
)
|
||||
]
|
||||
)
|
||||
(Decode.oneOf
|
||||
@ -208,7 +213,7 @@ action routeParams =
|
||||
(\result ->
|
||||
case result of
|
||||
Ok () ->
|
||||
Route.redirectTo Route.Index
|
||||
Route.redirectTo (Route.Posts__Slug___Edit { slug = okForm.slug })
|
||||
|
||||
Err errorMessage ->
|
||||
Server.Response.render { errors = formResponse, errorMessage = Just errorMessage }
|
||||
|
187
examples/blog-engine/app/Route/Posts/Slug_.elm
Normal file
187
examples/blog-engine/app/Route/Posts/Slug_.elm
Normal file
@ -0,0 +1,187 @@
|
||||
module Route.Posts.Slug_ exposing (ActionData, Data, route, RouteParams, Msg, Model)
|
||||
|
||||
{-|
|
||||
|
||||
@docs ActionData, Data, route, RouteParams, Msg, Model
|
||||
|
||||
-}
|
||||
|
||||
import BackendTask
|
||||
import BackendTask.Custom
|
||||
import Date exposing (Date)
|
||||
import Effect
|
||||
import ErrorPage
|
||||
import FatalError
|
||||
import Head
|
||||
import Html
|
||||
import Json.Decode as Decode exposing (Decoder)
|
||||
import Json.Encode as Encode
|
||||
import Markdown.Block exposing (Block)
|
||||
import Markdown.Parser
|
||||
import Markdown.Renderer
|
||||
import Pages.Msg
|
||||
import Pages.PageUrl
|
||||
import Path
|
||||
import Platform.Sub
|
||||
import Route
|
||||
import RouteBuilder
|
||||
import Server.Request
|
||||
import Server.Response
|
||||
import Shared
|
||||
import Time
|
||||
import View
|
||||
|
||||
|
||||
type alias Model =
|
||||
{}
|
||||
|
||||
|
||||
type Msg
|
||||
= NoOp
|
||||
|
||||
|
||||
type alias RouteParams =
|
||||
{ slug : String }
|
||||
|
||||
|
||||
route : RouteBuilder.StatefulRoute RouteParams Data ActionData Model Msg
|
||||
route =
|
||||
RouteBuilder.buildWithLocalState
|
||||
{ view = view
|
||||
, init = init
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
||||
(RouteBuilder.serverRender { data = data, action = action, head = head })
|
||||
|
||||
|
||||
init :
|
||||
Maybe Pages.PageUrl.PageUrl
|
||||
-> Shared.Model
|
||||
-> RouteBuilder.StaticPayload Data ActionData RouteParams
|
||||
-> ( Model, Effect.Effect Msg )
|
||||
init pageUrl sharedModel app =
|
||||
( {}, Effect.none )
|
||||
|
||||
|
||||
update :
|
||||
Pages.PageUrl.PageUrl
|
||||
-> Shared.Model
|
||||
-> RouteBuilder.StaticPayload Data ActionData RouteParams
|
||||
-> Msg
|
||||
-> Model
|
||||
-> ( Model, Effect.Effect msg )
|
||||
update pageUrl sharedModel app msg model =
|
||||
case msg of
|
||||
NoOp ->
|
||||
( model, Effect.none )
|
||||
|
||||
|
||||
subscriptions :
|
||||
Maybe Pages.PageUrl.PageUrl
|
||||
-> RouteParams
|
||||
-> Path.Path
|
||||
-> Shared.Model
|
||||
-> Model
|
||||
-> Sub Msg
|
||||
subscriptions maybePageUrl routeParams path sharedModel model =
|
||||
Platform.Sub.none
|
||||
|
||||
|
||||
type alias Data =
|
||||
{ body : List Block
|
||||
}
|
||||
|
||||
|
||||
type alias ActionData =
|
||||
{}
|
||||
|
||||
|
||||
data :
|
||||
RouteParams
|
||||
-> Server.Request.Parser (BackendTask.BackendTask FatalError.FatalError (Server.Response.Response Data ErrorPage.ErrorPage))
|
||||
data routeParams =
|
||||
Server.Request.succeed
|
||||
(BackendTask.Custom.run "getPost"
|
||||
(Encode.string routeParams.slug)
|
||||
(Decode.nullable postDecoder)
|
||||
|> BackendTask.allowFatal
|
||||
|> BackendTask.andThen
|
||||
(\maybePost ->
|
||||
case maybePost of
|
||||
Just post ->
|
||||
let
|
||||
parsed : Result String (List Block)
|
||||
parsed =
|
||||
post.body
|
||||
|> Markdown.Parser.parse
|
||||
|> Result.mapError (\_ -> "Invalid markdown.")
|
||||
in
|
||||
parsed
|
||||
|> Result.mapError FatalError.fromString
|
||||
|> Result.map
|
||||
(\parsedMarkdown ->
|
||||
Server.Response.render
|
||||
{ body = parsedMarkdown
|
||||
}
|
||||
)
|
||||
|> BackendTask.fromResult
|
||||
|
||||
Nothing ->
|
||||
Route.redirectTo Route.Index
|
||||
|> BackendTask.succeed
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
type alias Post =
|
||||
{ title : String
|
||||
, body : String
|
||||
, slug : String
|
||||
, publish : Maybe Date
|
||||
}
|
||||
|
||||
|
||||
postDecoder : Decoder Post
|
||||
postDecoder =
|
||||
Decode.map4 Post
|
||||
(Decode.field "title" Decode.string)
|
||||
(Decode.field "body" Decode.string)
|
||||
(Decode.field "slug" Decode.string)
|
||||
(Decode.field "publish"
|
||||
(Decode.nullable
|
||||
(Decode.int |> Decode.map (Time.millisToPosix >> Date.fromPosix Time.utc))
|
||||
)
|
||||
)
|
||||
|> Decode.map (Debug.log "postDecoder")
|
||||
|
||||
|
||||
head : RouteBuilder.StaticPayload Data ActionData RouteParams -> List Head.Tag
|
||||
head app =
|
||||
[]
|
||||
|
||||
|
||||
view :
|
||||
Maybe Pages.PageUrl.PageUrl
|
||||
-> Shared.Model
|
||||
-> Model
|
||||
-> RouteBuilder.StaticPayload Data ActionData RouteParams
|
||||
-> View.View (Pages.Msg.Msg Msg)
|
||||
view maybeUrl sharedModel model app =
|
||||
{ title = "Posts.Slug_"
|
||||
, body =
|
||||
[ Html.text "Here is your generated page!!!"
|
||||
, Html.div []
|
||||
(app.data.body
|
||||
|> Markdown.Renderer.render Markdown.Renderer.defaultHtmlRenderer
|
||||
|> Result.withDefault []
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
action :
|
||||
RouteParams
|
||||
-> Server.Request.Parser (BackendTask.BackendTask FatalError.FatalError (Server.Response.Response ActionData ErrorPage.ErrorPage))
|
||||
action routeParams =
|
||||
Server.Request.succeed (BackendTask.succeed (Server.Response.render {}))
|
298
examples/blog-engine/app/Route/Posts/Slug_/Edit.elm
Normal file
298
examples/blog-engine/app/Route/Posts/Slug_/Edit.elm
Normal file
@ -0,0 +1,298 @@
|
||||
module Route.Posts.Slug_.Edit exposing (ActionData, Data, route, RouteParams, Msg, Model)
|
||||
|
||||
{-|
|
||||
|
||||
@docs ActionData, Data, route, RouteParams, Msg, Model
|
||||
|
||||
-}
|
||||
|
||||
import BackendTask
|
||||
import BackendTask.Custom
|
||||
import Date exposing (Date)
|
||||
import Debug
|
||||
import Effect
|
||||
import ErrorPage
|
||||
import FatalError
|
||||
import Form
|
||||
import Form.Field
|
||||
import Form.FieldView
|
||||
import Form.Validation
|
||||
import Form.Value
|
||||
import Head
|
||||
import Html
|
||||
import Html.Attributes
|
||||
import Json.Decode as Decode exposing (Decoder)
|
||||
import Json.Encode as Encode
|
||||
import Pages.Msg
|
||||
import Pages.PageUrl
|
||||
import Pages.Script
|
||||
import Path
|
||||
import Platform.Sub
|
||||
import Route
|
||||
import RouteBuilder
|
||||
import Server.Request
|
||||
import Server.Response
|
||||
import Shared
|
||||
import Time
|
||||
import View
|
||||
|
||||
|
||||
type alias Model =
|
||||
{}
|
||||
|
||||
|
||||
type Msg
|
||||
= NoOp
|
||||
|
||||
|
||||
type alias RouteParams =
|
||||
{ slug : String }
|
||||
|
||||
|
||||
route : RouteBuilder.StatefulRoute RouteParams Data ActionData Model Msg
|
||||
route =
|
||||
RouteBuilder.buildWithLocalState
|
||||
{ view = view
|
||||
, init = init
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
||||
(RouteBuilder.serverRender { data = data, action = action, head = head })
|
||||
|
||||
|
||||
init :
|
||||
Maybe Pages.PageUrl.PageUrl
|
||||
-> Shared.Model
|
||||
-> RouteBuilder.StaticPayload Data ActionData RouteParams
|
||||
-> ( Model, Effect.Effect Msg )
|
||||
init pageUrl sharedModel app =
|
||||
( {}, Effect.none )
|
||||
|
||||
|
||||
update :
|
||||
Pages.PageUrl.PageUrl
|
||||
-> Shared.Model
|
||||
-> RouteBuilder.StaticPayload Data ActionData RouteParams
|
||||
-> Msg
|
||||
-> Model
|
||||
-> ( Model, Effect.Effect msg )
|
||||
update pageUrl sharedModel app msg model =
|
||||
case msg of
|
||||
NoOp ->
|
||||
( model, Effect.none )
|
||||
|
||||
|
||||
subscriptions :
|
||||
Maybe Pages.PageUrl.PageUrl
|
||||
-> RouteParams
|
||||
-> Path.Path
|
||||
-> Shared.Model
|
||||
-> Model
|
||||
-> Sub Msg
|
||||
subscriptions maybePageUrl routeParams path sharedModel model =
|
||||
Platform.Sub.none
|
||||
|
||||
|
||||
type alias Data =
|
||||
{ post : Post
|
||||
}
|
||||
|
||||
|
||||
type alias ActionData =
|
||||
{ errors : Form.Response String }
|
||||
|
||||
|
||||
type alias Post =
|
||||
{ title : String
|
||||
, body : String
|
||||
, slug : String
|
||||
, publish : Maybe Date
|
||||
}
|
||||
|
||||
|
||||
postDecoder : Decoder Post
|
||||
postDecoder =
|
||||
Decode.map4 Post
|
||||
(Decode.field "title" Decode.string)
|
||||
(Decode.field "body" Decode.string)
|
||||
(Decode.field "slug" Decode.string)
|
||||
(Decode.field "publish"
|
||||
(Decode.nullable
|
||||
(Decode.int |> Decode.map (Time.millisToPosix >> Date.fromPosix Time.utc))
|
||||
)
|
||||
)
|
||||
|> Decode.map (Debug.log "postDecoder")
|
||||
|
||||
|
||||
data :
|
||||
RouteParams
|
||||
-> Server.Request.Parser (BackendTask.BackendTask FatalError.FatalError (Server.Response.Response Data ErrorPage.ErrorPage))
|
||||
data routeParams =
|
||||
Server.Request.succeed
|
||||
(BackendTask.Custom.run "getPost"
|
||||
(Encode.string routeParams.slug)
|
||||
(Decode.nullable postDecoder)
|
||||
|> BackendTask.allowFatal
|
||||
|> BackendTask.map
|
||||
(\maybePost ->
|
||||
case maybePost of
|
||||
Just post ->
|
||||
Server.Response.render
|
||||
{ post = post
|
||||
}
|
||||
|
||||
Nothing ->
|
||||
Route.redirectTo Route.Index
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
head : RouteBuilder.StaticPayload Data ActionData RouteParams -> List Head.Tag
|
||||
head app =
|
||||
[]
|
||||
|
||||
|
||||
view :
|
||||
Maybe Pages.PageUrl.PageUrl
|
||||
-> Shared.Model
|
||||
-> Model
|
||||
-> RouteBuilder.StaticPayload Data ActionData RouteParams
|
||||
-> View.View (Pages.Msg.Msg Msg)
|
||||
view maybeUrl sharedModel model app =
|
||||
{ title = "Posts.Slug_.Edit"
|
||||
, body =
|
||||
[ Html.h2 [] [ Html.text "Form" ]
|
||||
, Form.renderHtml
|
||||
[]
|
||||
(\renderStyledHtmlUnpack -> Just renderStyledHtmlUnpack.errors)
|
||||
app
|
||||
app.data.post
|
||||
(Form.toDynamicTransition "form" form)
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
action :
|
||||
RouteParams
|
||||
-> Server.Request.Parser (BackendTask.BackendTask FatalError.FatalError (Server.Response.Response ActionData ErrorPage.ErrorPage))
|
||||
action routeParams =
|
||||
Server.Request.map
|
||||
(\( formResponse, parsedForm ) ->
|
||||
case parsedForm of
|
||||
Ok okForm ->
|
||||
BackendTask.Custom.run "updatePost"
|
||||
(Encode.object
|
||||
[ ( "slug", Encode.string okForm.slug )
|
||||
, ( "title", Encode.string okForm.title )
|
||||
, ( "body", Encode.string okForm.body )
|
||||
, ( "publish"
|
||||
, okForm.publish
|
||||
|> Maybe.map (Date.toIsoString >> Encode.string)
|
||||
|> Maybe.withDefault Encode.null
|
||||
)
|
||||
]
|
||||
)
|
||||
(Decode.succeed ())
|
||||
|> BackendTask.allowFatal
|
||||
|> BackendTask.map
|
||||
(\() ->
|
||||
Server.Response.render
|
||||
{ errors = formResponse }
|
||||
)
|
||||
|
||||
Err invalidForm ->
|
||||
BackendTask.map
|
||||
(\parsed ->
|
||||
Server.Response.render
|
||||
{ errors = formResponse }
|
||||
)
|
||||
(Pages.Script.log
|
||||
(Debug.toString parsedForm)
|
||||
)
|
||||
)
|
||||
(Server.Request.formData (Form.initCombined Basics.identity form))
|
||||
|
||||
|
||||
form : Form.DoneForm String ParsedForm Post (List (Html.Html (Pages.Msg.Msg Msg)))
|
||||
form =
|
||||
(\title slug body publish ->
|
||||
{ combine =
|
||||
ParsedForm
|
||||
|> Form.Validation.succeed
|
||||
|> Form.Validation.andMap title
|
||||
|> Form.Validation.andMap slug
|
||||
|> Form.Validation.andMap body
|
||||
|> Form.Validation.andMap publish
|
||||
, view =
|
||||
\formState ->
|
||||
let
|
||||
fieldView label field =
|
||||
Html.div []
|
||||
[ Html.label []
|
||||
[ Html.text (label ++ " ")
|
||||
, Form.FieldView.input [] field
|
||||
, errorsView formState.errors field
|
||||
]
|
||||
]
|
||||
in
|
||||
[ fieldView "title" title
|
||||
, fieldView "slug" slug
|
||||
, fieldView "body" body
|
||||
, fieldView "publish" publish
|
||||
, if formState.isTransitioning then
|
||||
Html.button [ Html.Attributes.disabled True ]
|
||||
[ Html.text "Submitting..." ]
|
||||
|
||||
else
|
||||
Html.button []
|
||||
[ Html.text "Submit" ]
|
||||
]
|
||||
}
|
||||
)
|
||||
|> Form.init
|
||||
|> Form.field "title"
|
||||
(Form.Field.required "Required" Form.Field.text
|
||||
|> Form.Field.withInitialValue (.title >> Form.Value.string)
|
||||
)
|
||||
|> Form.field "slug"
|
||||
(Form.Field.required "Required" Form.Field.text
|
||||
|> Form.Field.withInitialValue (.slug >> Form.Value.string)
|
||||
)
|
||||
|> Form.field "body"
|
||||
(Form.Field.required "Required" Form.Field.text
|
||||
|> Form.Field.textarea { rows = Just 30, cols = Just 80 }
|
||||
|> Form.Field.withInitialValue (.body >> Form.Value.string)
|
||||
)
|
||||
|> Form.field "publish"
|
||||
(Form.Field.date { invalid = \dateUnpack -> "" }
|
||||
|> Form.Field.withOptionalInitialValue
|
||||
(.publish >> Maybe.map Form.Value.date)
|
||||
)
|
||||
|
||||
|
||||
type alias ParsedForm =
|
||||
{ title : String, slug : String, body : String, publish : Maybe Date }
|
||||
|
||||
|
||||
errorsView :
|
||||
Form.Errors String
|
||||
-> Form.Validation.Field String parsed kind
|
||||
-> Html.Html (Pages.Msg.Msg Msg)
|
||||
errorsView errors field =
|
||||
if List.isEmpty (Form.errorsForField field errors) then
|
||||
Html.div [] []
|
||||
|
||||
else
|
||||
Html.div
|
||||
[]
|
||||
[ Html.ul
|
||||
[]
|
||||
(List.map
|
||||
(\error ->
|
||||
Html.li
|
||||
[ Html.Attributes.style "color" "red" ]
|
||||
[ Html.text error ]
|
||||
)
|
||||
(Form.errorsForField field errors)
|
||||
)
|
||||
]
|
@ -3,13 +3,14 @@ import { PrismaClientKnownRequestError } from "@prisma/client/runtime/index.js";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export async function createPost({ slug, title, body }) {
|
||||
export async function createPost({ slug, title, body, publish }) {
|
||||
try {
|
||||
await prisma.post.create({
|
||||
data: {
|
||||
slug,
|
||||
title,
|
||||
body,
|
||||
publish,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
@ -27,23 +28,58 @@ export async function createPost({ slug, title, body }) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function posts() {
|
||||
return (await prisma.post.findMany()).map(transformDates);
|
||||
}
|
||||
export async function updatePost({ slug, title, body, publish }) {
|
||||
try {
|
||||
const data = {
|
||||
slug,
|
||||
title,
|
||||
body,
|
||||
publish: new Date(publish),
|
||||
};
|
||||
await prisma.post.upsert({
|
||||
where: {
|
||||
slug,
|
||||
},
|
||||
create: data,
|
||||
update: data,
|
||||
});
|
||||
return null;
|
||||
} catch (e) {
|
||||
if (e instanceof PrismaClientKnownRequestError) {
|
||||
// https://www.prisma.io/docs/reference/api-reference/error-reference
|
||||
console.log("MESSAGE:", e.message, e.meta, e.code, e.name);
|
||||
console.dir(e);
|
||||
|
||||
function transformDates(item) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(item).map(([key, value]) => [key, transformValue(value)])
|
||||
);
|
||||
}
|
||||
return { errorMessage: e.message };
|
||||
|
||||
function transformValue(value) {
|
||||
// if (typeof value === "bigint") {
|
||||
// obj[key] = toNumber(value);
|
||||
// }
|
||||
if (value instanceof Date) {
|
||||
return value.getMilliseconds();
|
||||
} else {
|
||||
return value;
|
||||
// specific error
|
||||
} else {
|
||||
console.trace(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPost(slug) {
|
||||
try {
|
||||
return await prisma.post.findFirst({
|
||||
where: {
|
||||
slug,
|
||||
},
|
||||
select: {
|
||||
body: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
publish: true,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("ERROR");
|
||||
console.trace(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function posts() {
|
||||
return await prisma.post.findMany();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user