From a974a59121a315decde327bc388364fe40cd10f7 Mon Sep 17 00:00:00 2001 From: Xnoe Date: Thu, 19 May 2022 16:10:10 +0100 Subject: [PATCH] Added the ability to view posts. --- backend/app/Main.hs | 5 +- frontend/src/Main.elm | 145 ++++++++++++++++++++++++++++++------------ nginx/nginx.conf | 3 +- 3 files changed, 111 insertions(+), 42 deletions(-) diff --git a/backend/app/Main.hs b/backend/app/Main.hs index 3dd465f..117b100 100644 --- a/backend/app/Main.hs +++ b/backend/app/Main.hs @@ -43,7 +43,7 @@ getPostListing c page = do let sub = ("subtext", toJSString $ fromSql subtext) let cat = ("category", toJSString category) let id = ("id", toJSString $ show $ (fromSql pid::Int)) - return $ showJSON $ toJSObject [title, sub, cat] + return $ showJSON $ toJSObject [id, title, sub, cat] ) result return $ Just $ showJSON $ JSArray posts @@ -74,7 +74,8 @@ getPostsInCategory c cat page = do let title = ("title", toJSString $ fromSql t) let sub = ("subtext", toJSString $ fromSql subtext) let id = ("id", toJSString $ show $ (fromSql pid::Int)) - return $ showJSON $ toJSObject [title, sub] + let cat = ("category", toJSString category) + return $ showJSON $ toJSObject [id, cat, title, sub] ) result return $ Just $ showJSON $ toJSObject [("category", showJSON $ toJSString category), ("posts", showJSON $ JSArray posts)] diff --git a/frontend/src/Main.elm b/frontend/src/Main.elm index f3af671..5c57dc0 100644 --- a/frontend/src/Main.elm +++ b/frontend/src/Main.elm @@ -1,7 +1,7 @@ module Main exposing (..) import Http -import Json.Decode exposing (Decoder, field, string, map3) +import Json.Decode exposing (Decoder, field, string, map2, map3, map4) import Browser import Browser.Navigation exposing (..) @@ -9,6 +9,8 @@ import Url import Html exposing (..) import Html.Attributes exposing (..) +import Url.Parser as P exposing (()) + main : Program () Model Msg main = Browser.application { @@ -20,43 +22,99 @@ main = onUrlChange = UrlChanged } -type alias GridItem = { +type alias Post = { + id: String, category: String, title: String, subtext: String } +type alias Category = { + id: String, + name: String + } + type alias Model = { - header: Maybe (Html Msg), - sidebar: Maybe (Html Msg), - pinnedPosts: List GridItem, - posts: List GridItem, - url: Url.Url, + header: String, + content: String, + footer: String, + pinnedPosts: List Post, + posts: List Post, + route: Maybe Route, key: Browser.Navigation.Key, errMessage: Maybe (String) } +type Route + = Home + | CategoriesView + | CategoryView String + | PostView String + | Login + | Logout + | CreatePost + +routeParser : P.Parser (Route -> a) a +routeParser = + P.oneOf + [ P.map Home P.top + , P.map CategoriesView (P.s "categories") + , P.map CategoryView (P.s "category" P.string) + , P.map PostView (P.s "post" P.string) + , P.map Login (P.s "login") + , P.map Logout (P.s "logout") + , P.map CreatePost (P.s "create") + ] + +messageOfRoute : Maybe Route -> Cmd Msg +messageOfRoute r = + case r of + Just route -> + case route of + Home -> Http.get {url = "/v1/posts", expect = Http.expectJson GotPosts processPostListing} + CategoriesView -> Http.get {url = "/v1/categories", expect = Http.expectJson GotCategories processCategoryListing} + PostView p -> Http.get {url = "/v1/post/" ++ p, expect = Http.expectJson GotPost processPost} + CategoryView c -> Http.get {url = "/v1/category/" ++ c, expect = Http.expectJson GotCategory processCategory} + _ -> Cmd.none + Nothing -> Cmd.none + init : () -> Url.Url -> Browser.Navigation.Key -> (Model, Cmd Msg) init _ url key = + let r = P.parse routeParser url in ({ - header = Nothing, sidebar = Nothing, pinnedPosts = [], posts = [], - url = url, key = key, errMessage = Nothing + header = "", content = "", footer = "", pinnedPosts = [], + posts = [], route = r, key = key, errMessage = Just "Loading Posts..." }, - Http.get {url = "v1/posts", expect = Http.expectJson GotPosts processPosts} + messageOfRoute r ) type Msg = LinkClinked Browser.UrlRequest | UrlChanged Url.Url - | GotPosts (Result Http.Error (List GridItem)) + | GotPosts (Result Http.Error (List Post)) + | GotCategories (Result Http.Error (List Category)) + | GotPost (Result Http.Error (String, String, String)) + | GotCategory (Result Http.Error (String, List Post)) -processPosts : Decoder (List GridItem) -processPosts = +processPostListing : Decoder (List Post) +processPostListing = Json.Decode.list ( - map3 GridItem (field "category" string) (field "title" string) (field "subtext" string) + map4 Post (field "id" string) (field "category" string) (field "title" string) (field "subtext" string) ) +processCategoryListing : Decoder (List Category) +processCategoryListing = + Json.Decode.list (map2 Category (field "id" string) (field "name" string)) + +processPost : Decoder (String, String, String) +processPost = + map3 (\a -> \b -> \c -> (a,b,c)) (field "title" string) (field "content" string) (field "category" string) + +processCategory : Decoder (String, List Post) +processCategory = + map2 (\a -> \b -> (a,b)) (field "category" string) (field "posts" processPostListing) + update : Msg -> Model -> (Model, Cmd Msg) update msg model = case msg of @@ -66,19 +124,41 @@ update msg model = Browser.External url -> (model, Browser.Navigation.load url) UrlChanged req -> ( - { model | url = req } - , Cmd.none + let r = P.parse routeParser req in + ({model | route = r}, messageOfRoute r) ) - GotPosts (Ok l) -> ({model | posts = l}, Cmd.none) - GotPosts (Err _) -> ({model | errMessage = Just "Failed to load posts!"}, Cmd.none) + GotPosts (Ok l) -> ({model | errMessage = Nothing, posts = l}, Cmd.none) + GotPosts (Err _) -> ({model | errMessage = Just "Failed to load posts"}, Cmd.none) + GotPost (Ok (title, content, category)) -> ({model | errMessage = Nothing, header = title, content = content, footer = category}, Cmd.none) + _ -> ({model | errMessage = Just "Something has gone horribly wrong."}, Cmd.none) type alias Document msg = { title: String, body: List (Html msg) } -renderGridItem : GridItem -> Html Msg -renderGridItem griditem = +view : Model -> Document Msg +view model = + {title = "Xnopyt.com", body = [htmlView model]} + +htmlView : Model -> Html Msg +htmlView model = + renderModel model + +renderModel : Model -> Html Msg +renderModel model = + case (model.errMessage) of + Just e -> h1 [] [text (e)] + Nothing -> + case model.route of + Just route -> case route of + Home -> cardListing model + PostView _ -> div [] [h1 [] [text (model.header)], p [] [text(model.content)], h3 [] [text(model.footer), a [href "/"] [text "Go Home"]]] + _ -> h1 [] [ text ("Not found."), a [ href "/"] [text("Return home")] ] + _ -> h1 [] [ text ("Not found."), a [ href "/"] [text("Return home")] ] + +renderPost : Post -> Html Msg +renderPost post = div [ style "width" "25%", style "height" "auto", @@ -104,7 +184,7 @@ renderGridItem griditem = style "font-weight" "bold", style "border-radius" "10px 10px 0 0" ] [ - text (griditem.title) + a [href ("/post/" ++ post.id)] [text (post.title)] ], div [ style "color" "white", @@ -112,7 +192,7 @@ renderGridItem griditem = style "background-color" "#505050", style "padding" "10px" ] [ - text (griditem.subtext) + text (post.subtext) ], footer [ style "background-color" "#404040", @@ -126,27 +206,14 @@ renderGridItem griditem = style "padding" "10px", style "border-radius" "0 0 10px 10px" ] [ - text (griditem.category) + text (post.category) ] ] ] -renderModel : Model -> Html Msg -renderModel model = +cardListing : Model -> Html Msg +cardListing model = div [ style "display" "flex", style "flex-flow" "row wrap" - ] - (case (model.errMessage) of - Nothing -> (List.map renderGridItem model.posts) - Just e -> [h1 [] [text (e)]] - ) - - -view : Model -> Document Msg -view model = - {title = "Xnopyt.com", body = [htmlView model]} - -htmlView : Model -> Html Msg -htmlView model = - renderModel model \ No newline at end of file + ] (List.map renderPost model.posts) \ No newline at end of file diff --git a/nginx/nginx.conf b/nginx/nginx.conf index d2a699a..428df56 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -11,7 +11,8 @@ http { } location / { - proxy_pass http://frontend:80; + rewrite ^/.* / break; + proxy_pass http://frontend:80/index.html; } } } \ No newline at end of file