diff of a87d76b84ccff6e737fc47b0ac66850c32bd400b

a87d76b84ccff6e737fc47b0ac66850c32bd400b
diff --git a/elm-frontti/src/Ajax_cmds.elm b/elm-frontti/src/Ajax_cmds.elm
index ed1aea4..c637693 100644
--- a/elm-frontti/src/Ajax_cmds.elm
+++ b/elm-frontti/src/Ajax_cmds.elm
@@ -150,3 +150,9 @@ addFeed newFeed =
         { url = "/api/user/feeds"
         , body = Http.jsonBody (Feeds.newFeedEncoder newFeed)
         , expect = Http.expectWhatever FeedAdded }
+
+markFeedItemRead feed_id item_id =
+    Http.post
+        { url = "/api/user/feeds/" ++ feed_id ++ "/" ++ item_id ++ "/mark-read"
+        , body = Http.emptyBody
+        , expect = Http.expectWhatever FeedItemReadResponse}
diff --git a/elm-frontti/src/FeedView.elm b/elm-frontti/src/FeedView.elm
index 074dc85..1a0e794 100644
--- a/elm-frontti/src/FeedView.elm
+++ b/elm-frontti/src/FeedView.elm
@@ -16,35 +16,43 @@ import Tab exposing (tabs)
 import Random
 
 feed_item time_format zone item =
-    li [] [ h1 [] [ a [ href item.link ] [ text item.title] ]
-          , (if item.title == "" then 
-                  h4 [] [ a [ href item.link] [ text (formatDateTime time_format zone item.pubdate) ]]
-             else
-                 h4 [] [ text (formatDateTime time_format zone item.pubdate)])
-          , div [ class "feed-author"] [ text <| "By " ++ item.author]
-          , div [ class "feed-item"
-                , dangerouslySetInnerHTML item.description] []]
+    case item.feed_id of
+        Just feed_id ->
+            li [] [ h1 [] [ a [ href item.link ] [ text item.title] ]
+                  , (if item.title == "" then 
+                         h4 [] [ a [ href item.link] [ text (formatDateTime time_format zone item.pubdate) ]]
+                     else
+                         h4 [] [ text (formatDateTime time_format zone item.pubdate)])
+                  , div [ class "feed-read"] [
+                         let is_read = item.is_read in
+                         button [ onClick <| ReadFeedItem feed_id item.id (not is_read) ] [ text "Mark as read"]]
+                  , div [ class "feed-author"] [ text <| "By " ++ item.author]
+                  , div [ class "feed-item"
+                        , dangerouslySetInnerHTML item.description] []]
+        Nothing ->
+            li [] [ text "Unknown feed" ]
 
-correctlySortedFeedItemList settings zone items = 
+correctlySortedFeedItemList show_archived settings zone items = 
     (  items
     |> List.sortBy (Time.posixToMillis << .pubdate)
     |> List.reverse
+    |> List.filter (\item -> (not item.is_read) || show_archived)
     |> List.map (feed_item settings.time_format zone))
 
 -- fs = feeds, elm sucks balls at shadowing
-perFeedView settings zone fs new_feed_state = 
+perFeedView show_archived settings zone fs new_feed_state = 
     ul [ class "feed-list" ]
         (List.map (\feed ->
                        li [ class "feed" ]
                        [ header [] [ text feed.name ]
                        , a [ href feed.url ] [ text feed.url ]
                        , ul [ class "feed-items" ]
-                           (correctlySortedFeedItemList settings zone feed.items)]) fs)
+                           (correctlySortedFeedItemList show_archived settings zone <| List.map (\i -> { i | feed_id = Just feed.id}) feed.items)]) fs)
             
-singleFeedView settings zone fs =
-    let feed = List.concatMap .items fs in 
+singleFeedView show_archived settings zone fs =
+    let final_feed = List.concatMap (\feed -> List.map (\item -> { item | feed_id = Just feed.id}) feed.items) fs in 
     ul [ class "feed-items" ]
-        (correctlySortedFeedItemList settings zone feed)
+        (correctlySortedFeedItemList show_archived settings zone final_feed)
               
 
 readerState_str state =
@@ -52,15 +60,19 @@ readerState_str state =
         PerFeed -> "PerFeed"
         SingleFeed -> "SingleFeed"
                              
-feeds feedReaderState settings zone fs new_feed  =
+feeds feedReaderState show_archived settings zone fs new_feed  =
     let new_feed_state = Maybe.withDefault (NewFeed "" "") new_feed
     in
         div [ id "feeds" ] 
-            [ tabs "rss-feed-tab" (readerState_str feedReaderState)
+            [ label [] [ input [ type_ "checkbox"
+                               , checked show_archived
+                               , onClick <| ShowArchivedFeedItems (not show_archived)] []
+                       , text "Show read items"]
+            , tabs "rss-feed-tab" (readerState_str feedReaderState)
                   (Dict.fromList [ ("PerFeed", "Group by feed")
                                  , ("SingleFeed", "Show all in a feed")])
-                  (Dict.fromList [ ("PerFeed", perFeedView settings zone fs new_feed_state)
-                                 , ("SingleFeed", singleFeedView settings zone fs)])
+                  (Dict.fromList [ ("PerFeed", perFeedView show_archived settings zone fs new_feed_state)
+                                 , ("SingleFeed", singleFeedView show_archived settings zone fs)])
                   
             , h3 [] [ text "Add new feed?"]
             , label [ for "name" ] [ text "Feed name" ]
diff --git a/elm-frontti/src/Feeds.elm b/elm-frontti/src/Feeds.elm
index 7c70631..620efdc 100644
--- a/elm-frontti/src/Feeds.elm
+++ b/elm-frontti/src/Feeds.elm
@@ -20,7 +20,10 @@ type alias Item =
     , description: String 
     , link: String
     , author: String
-    , pubdate: Time.Posix} 
+    , pubdate: Time.Posix
+    , is_read: Bool
+    -- parent id that's required for ui to function correctly 
+    , feed_id: Maybe UUID} 
 
 type alias Feed =
     { id: UUID
@@ -45,6 +48,7 @@ descriptionDecoder = Decode.field "description" Decode.string
 linkDecoder = Decode.field "link" Decode.string
 authorDecoder = Decode.field "author" Decode.string
 pubdateDecoder = Decode.field "pubdate" Extra.iso8601
+is_readDecoder = Decode.field "is_read" Decode.bool
 
 itemDecoder =
     Decode.succeed Item
@@ -55,6 +59,8 @@ itemDecoder =
         |> decodeApply linkDecoder
         |> decodeApply authorDecoder
         |> decodeApply pubdateDecoder
+        |> decodeApply is_readDecoder
+        |> decodeApply (Decode.succeed Nothing)
 
 itemsDecoder =
     Decode.field "items" (Decode.list itemDecoder)
diff --git a/elm-frontti/src/Main.elm b/elm-frontti/src/Main.elm
index b317c59..7d95970 100644
--- a/elm-frontti/src/Main.elm
+++ b/elm-frontti/src/Main.elm
@@ -571,7 +571,7 @@ update msg model =
         FeedsReceived result -> 
             case result of
                 Ok feeds ->
-                    ( { model | view_state = Feeds feeds}
+                    ( { model | view_state = Feeds feeds False}
                     , Cmd.none)
                 Err error -> 
                     ( { model | view_state = ShowError (errToString error) }
@@ -629,6 +629,38 @@ update msg model =
                     , Cmd.none)
                 _ -> ( model
                      , alert <| "Unknown tab " ++ tab_id)
+        ReadFeedItem feed_id item_id is_read ->
+            case model.view_state of
+                Feeds feeds show_archived ->
+                    let new_feeds = (  feeds
+                                    |> List.map (\f -> if f.id == feed_id then
+                                                           {f | items = (  f.items
+                                                                        |> List.map (\item ->
+                                                                                         if item.id == item_id then
+                                                                                             {item | is_read = is_read}
+                                                                                         else
+                                                                                             item ))}
+                                                       else
+                                                           f))
+                    in
+                        ({ model | view_state = Feeds new_feeds show_archived}
+                        , markFeedItemRead (UUID.toString feed_id) (UUID.toString item_id))
+                _ -> ( model
+                     , Cmd.none)
+        ShowArchivedFeedItems showArchived ->
+            case model.view_state of
+                Feeds feeds _ -> 
+                    ({ model
+                         | view_state = Feeds feeds showArchived}
+                    , Cmd.none)
+                _ -> ( model
+                     , Cmd.none)
+        FeedItemReadResponse result ->
+            case result of
+                Ok _ -> ( model
+                        , Cmd.none)
+                Err error -> ( { model | view_state = ShowError (errToString error) }
+                             , Cmd.none)
 
                     
 doGoHome_ model other_cmds =
@@ -722,7 +754,7 @@ view model =
                                                    Nothing -> [ div [] [ text "No post loaded" ]]
                                            MediaList -> [ medialist model.loadedImages model.medialist_state ]
                                            SettingsEditor -> [ SettingsEditor.editor settings]
-                                           Feeds feeds -> [ FeedView.feeds model.feedReaderState settings model.zone feeds model.new_feed ])
+                                           Feeds feeds show_archived -> [ FeedView.feeds model.feedReaderState show_archived settings model.zone feeds model.new_feed ])
                         , div [id "sidebar"] [ User.loginView model.loginState
                                              , (sidebarHistory model.titles )
                                              , (case model.view_state of
diff --git a/elm-frontti/src/Message.elm b/elm-frontti/src/Message.elm
index ec3cc0c..634b344 100644
--- a/elm-frontti/src/Message.elm
+++ b/elm-frontti/src/Message.elm
@@ -30,7 +30,7 @@ type ViewState
     | MediaList                     -- list all the image blobs in db
     | TaggedPostsView (List Article.Article)
     | SettingsEditor
-    | Feeds (List Feeds.Feed)
+    | Feeds (List Feeds.Feed) Bool -- <- show_archived?
       
 type alias User =
     { username : String
@@ -163,6 +163,9 @@ type Msg
   | FeedAdded (Result Http.Error ())
   | SetPerFeedView
   | SelectTab String String
+  | ReadFeedItem UUID UUID Bool
+  | ShowArchivedFeedItems Bool
+  | FeedItemReadResponse (Result Http.Error ())
 
 -- ports
 port reallySetupAce : String -> Cmd msg
diff --git a/resources/sql/021-more-rss-reader-stuff.sql b/resources/sql/021-more-rss-reader-stuff.sql
new file mode 100644
index 0000000..0e26653
--- /dev/null
+++ b/resources/sql/021-more-rss-reader-stuff.sql
@@ -0,0 +1,2 @@
+ALTER TABLE blog.feed_item
+ADD COLUMN read_at TIMESTAMP NULL DEFAULT NULL;
diff --git a/resources/sql/reader-fns.sql b/resources/sql/reader-fns.sql
index 7c058e1..581f69a 100644
--- a/resources/sql/reader-fns.sql
+++ b/resources/sql/reader-fns.sql
@@ -32,3 +32,13 @@ INSERT INTO blog.feed_subscription(name, url, owner) VALUES ($1, $2, $3);
 -- name: insert-feed-item @execute
 INSERT INTO blog.feed_item(title, link, description, author, pubdate, feed)
 VALUES ($1, $2, $3, $4, to_timestamp($5), $6);
+
+-- name: mark-as-read
+UPDATE blog.feed_item fi 
+SET read_at = now()
+FROM blog.feed_subscription fs
+WHERE fs.id = fi.feed
+      AND fi.id = $1
+      AND fi.feed = $2
+      AND fs.owner = $3
+RETURNING *;
diff --git a/src/migration-list.lisp b/src/migration-list.lisp
index 411322b..fb68da0 100644
--- a/src/migration-list.lisp
+++ b/src/migration-list.lisp
@@ -25,6 +25,7 @@
 (defmigration "018-previously")
 (defmigration "019-rss-settings")
 (defmigration "020-rss-reader-stuff")
+(defmigration "021-more-rss-reader-stuff")
 
 (defun prepare-e2e-migration ()
   (postmodern:execute "DELETE FROM blog.Users")
diff --git a/src/routes/rss-reader-routes.lisp b/src/routes/rss-reader-routes.lisp
index f316875..f5c5bbf 100644
--- a/src/routes/rss-reader-routes.lisp
+++ b/src/routes/rss-reader-routes.lisp
@@ -31,6 +31,13 @@
     (setf (hunchentoot:return-code*) 204)
     ""))
 
+(defroute mark-as-read ("/api/user/feeds/:feed-id/:item-id/mark-read" :method :post
+								      :decorators (@transaction
+										   @authenticated)) ()
+  (murja.rss.reader-db:mark-as-read item-id feed-id (gethash "id" *user*))
+  (setf (hunchentoot:return-code*) 204)
+  "")
+
 ;; This will be called by cron/curl
 (defroute update-feeds-rotue ("/api/rss/update" :method :get
 						:decorators (@transaction)) ()
diff --git a/src/rss/reader-db.lisp b/src/rss/reader-db.lisp
index 650dd17..d320a86 100644
--- a/src/rss/reader-db.lisp
+++ b/src/rss/reader-db.lisp
@@ -3,7 +3,7 @@
   (:import-from :halisql :defqueries)
   (:import-from :lisp-fixup :partial :compose)
   (:import-from :cl-date-time-parser :parse-date-time)
-  (:export :get-user-feeds :subscribe-to-feed))
+  (:export :get-user-feeds :subscribe-to-feed :mark-as-read))
 	
 (in-package :murja.rss.reader-db)
 
@@ -15,10 +15,22 @@
   hashmap)
 
 (defun get-user-feeds (user-id)
-  (let ((feeds (coerce (get-user-feeds* user-id) 'list)))
-    (mapcar (compose (partial #'parse "items")
-		     (partial #'parse "creator"))
-	    feeds)))
+  (let* ((feeds (coerce (get-user-feeds* user-id) 'list))
+	 (fixed-feeds
+	   (mapcar (compose (partial #'parse "items")
+			    (partial #'parse "creator"))
+		   feeds)))
+    (dolist (feed fixed-feeds)
+      (setf (gethash "items" feed)
+	    (coerce (gethash "items" feed) 'list))
+      (dolist (item (gethash "items" feed))
+        (setf (gethash "is_read" item)
+	      (not (equalp 'NULL (gethash "read_at" item))))
+
+	;; frontend doesn't need this
+	(remhash "read_at" item)))
+
+    fixed-feeds))
 
 (defun subscribe-to-feed (feed-name feed-url owner)
   (insert-feed feed-name feed-url (gethash "id" owner)))