From 1e661031fbde0d9aabe51a528c0797dcbeb42e52 Mon Sep 17 00:00:00 2001 From: Stuce Date: Thu, 3 Jul 2025 09:42:31 +0100 Subject: [PATCH 1/2] Created a route to fetch recent modifications as a csv to allow creating clients --- client_session_key.aes | 1 + config/models.persistentmodels | 3 ++ config/routes.yesodroutes | 1 + src/Application.hs | 1 + src/Foundation.hs | 9 ++++-- src/Handler/Api.hs | 52 ++++++++++++++++++++++++++++++++++ src/Handler/Group.hs | 12 +++----- src/Handler/Todolist.hs | 3 +- 8 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 client_session_key.aes create mode 100644 src/Handler/Api.hs diff --git a/client_session_key.aes b/client_session_key.aes new file mode 100644 index 0000000..70e3c9c --- /dev/null +++ b/client_session_key.aes @@ -0,0 +1 @@ +:M^O2I蚑4,pe8ķ,CI(q7/gGuL'(ɜwq1I#pYW)2L{2;v_i[ʳY \ No newline at end of file diff --git a/config/models.persistentmodels b/config/models.persistentmodels index bb9d354..d9a31b5 100644 --- a/config/models.persistentmodels +++ b/config/models.persistentmodels @@ -8,12 +8,15 @@ TodolistItem Todolist groupId GroupId OnDeleteCascade title Text + lastModified UTCTime UniqueListPair groupId title + deriving Show User name Text UniqueName name Group group Text + lastModified UTCTime GroupUser user UserId groupId GroupId OnDeleteCascade \ No newline at end of file diff --git a/config/routes.yesodroutes b/config/routes.yesodroutes index 9d7e398..b310a04 100644 --- a/config/routes.yesodroutes +++ b/config/routes.yesodroutes @@ -26,3 +26,4 @@ /delete DeleteGroupR POST /delete/group/#GroupId DeleteTodolistR POST +/api/#Int ApiR GET diff --git a/src/Application.hs b/src/Application.hs index fd6e107..7be018c 100644 --- a/src/Application.hs +++ b/src/Application.hs @@ -47,6 +47,7 @@ import Handler.Common import Handler.Group import Handler.Todolist import Handler.TodolistItem +import Handler.Api -- This line actually creates our YesodDispatch instance. It is the second half -- of the call to mkYesodData which occurs in Foundation.hs. Please see the diff --git a/src/Foundation.hs b/src/Foundation.hs index d031441..bdd1015 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -12,7 +12,7 @@ module Foundation where import Import.NoFoundation import Data.Kind (Type) -import Database.Persist.Sql (ConnectionPool, runSqlPool) +import Database.Persist.Sql (ConnectionPool, runSqlPool, rawSql) import Text.Hamlet (hamletFile) import Text.Jasmine (minifym) import Control.Monad.Logger (LogSource) @@ -208,4 +208,9 @@ dbIfAuth groupId action = do user <- getUserId result <- runDB $ selectFirst [GroupUserUser ==. user, GroupUserGroupId ==. groupId] [] if isNothing result then permissionDenied "you are not logged in or you dont have access to this group" - else runDB action \ No newline at end of file + else runDB action + +getGroups :: Key User -> Handler [Entity Group] +getGroups userId = + let sql = "SELECT ?? FROM \"group\" JOIN group_user gu ON \"group\".id = gu.group_id WHERE gu.user = ?" in + runDB $ rawSql sql [toPersistValue userId] \ No newline at end of file diff --git a/src/Handler/Api.hs b/src/Handler/Api.hs new file mode 100644 index 0000000..f429a0e --- /dev/null +++ b/src/Handler/Api.hs @@ -0,0 +1,52 @@ +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE OverloadedStrings #-} + +module Handler.Api where + +import Import +import Database.Persist.Sql (rawSql) +import Data.Time.Clock.POSIX (posixSecondsToUTCTime, utcTimeToPOSIXSeconds) +import qualified Data.Text as Text +getApiR :: Int -> Handler TypedContent +getApiR time = do + -- TODO: use only one runDB + userId <- getUserId + -- We get all groups no matter what, since else we can't know which groups have been deleted + groups <- getGroups userId + let utcTime = posixSecondsToUTCTime (fromIntegral time) + let sqlLists = "select ?? from todolist join group_user on todolist.group_id = group_user.group_id and group_user.user = ? join \"group\" on todolist.group_id = \"group\".id and \"group\".last_modified > ?;" + lists <- runDB $ rawSql sqlLists [toPersistValue userId, toPersistValue utcTime] + let a = lists :: [Entity Todolist] + let sqlItems = "select ?? from todolist join group_user on todolist.group_id = group_user.group_id and group_user.user = ? join \"group\" on todolist.group_id = \"group\".id and \"group\".last_modified > ? join todolist_item on todolist_item.todolist_id = todolist.id;" + items <- runDB $ rawSql sqlItems [toPersistValue userId, toPersistValue utcTime] + let t = unlines $ map groupToCSV groups <> map todolistToCSV lists <> map todolistItemToCSV items + return $ TypedContent typePlain $ toContent t + +todolistItemToCSV :: Entity TodolistItem -> Text +todolistItemToCSV item = "i," <> fieldToText item +todolistToCSV :: Entity Todolist -> Text +todolistToCSV list = "l," <> fieldToText list +groupToCSV :: Entity Group -> Text +groupToCSV group = "g," <> fieldToText group + +-- TODO: error management ? (maybe use Either Text Text and then propagate left to handler and send error ?) +fieldToText :: PersistEntity record => Entity record -> Text +fieldToText field = Text.intercalate "," (map persistValueToText $ entityValues field) + +persistValueToText :: PersistValue -> Text +persistValueToText (PersistText s) = s +persistValueToText (PersistInt64 i) = Text.pack $ show i +persistValueToText (PersistUTCTime d) = Text.pack $ show $ floor (utcTimeToPOSIXSeconds d) +persistValueToText (PersistBool b) = if b then "T" else "F" +persistValueToText _ = error "Wrong input type" + +getText :: Text +getText = do + -- GET EVERY GROUP THAT HAS BEEN MODIFIED SINCE TIMESTAMP FROM USER + + -- GET EVERY TODOLIST THAT HAS BEEN MODIFIED SINCE TIMESTAMP + -- GET EVERY ITEM FROM THESE TODOLISTS + -- ENCODE ALL OF THEM IN THE TEXTFILE + -- SEND IT ! + -- DONE :) + error "not done yet" \ No newline at end of file diff --git a/src/Handler/Group.hs b/src/Handler/Group.hs index 16fc7c1..eb15bea 100644 --- a/src/Handler/Group.hs +++ b/src/Handler/Group.hs @@ -11,7 +11,7 @@ module Handler.Group where import Import import Text.Read -import Database.Persist.Sql (fromSqlKey, toSqlKey, rawSql) +import Database.Persist.Sql (fromSqlKey, toSqlKey) getGroupR :: Handler Html getGroupR = do userId <- getUserId @@ -25,7 +25,8 @@ postAddGroupR = do -- TODO: in a newer version, put insertUnique_ user <- getUserId _ <- runDB $ do - gId <- insert $ Group g + currentTime <- liftIO getCurrentTime + gId <- insert $ Group g currentTime success <- insertUnique $ GroupUser user gId when (isNothing success) $ delete gId redirect GroupR @@ -54,9 +55,4 @@ postDeleteGroupR = do let ids = map toSqlKey ints :: [GroupId] -- TODO: make sure the user has access to it aswell (this only works now for single user), and handle group owned by many runDB $ deleteWhere [GroupId <-. ids] - redirect EditGroupR - -getGroups :: Key User -> Handler [Entity Group] -getGroups userId = - let sql = "SELECT ?? FROM \"group\" JOIN group_user gu ON \"group\".id = gu.group_id WHERE gu.user = ?" in - runDB $ rawSql sql [toPersistValue userId] \ No newline at end of file + redirect EditGroupR \ No newline at end of file diff --git a/src/Handler/Todolist.hs b/src/Handler/Todolist.hs index 7dcfbeb..1520130 100644 --- a/src/Handler/Todolist.hs +++ b/src/Handler/Todolist.hs @@ -16,7 +16,8 @@ postAddTodolistR :: GroupId -> Handler Html postAddTodolistR groupId = do list <- runInputPost $ ireq textField "list" -- TODO: in a newer version, put insertUnique_ - _ <- dbIfAuth groupId (insertUnique $ Todolist groupId list) + currentTime <- liftIO getCurrentTime + _ <- dbIfAuth groupId (insertUnique $ Todolist groupId list currentTime) redirect $ TodolistR groupId -- TODO: move this to a new handler file From 67d88bd31b2860bd995b461b1974e6939a0da591 Mon Sep 17 00:00:00 2001 From: Stuce Date: Thu, 3 Jul 2025 09:44:55 +0100 Subject: [PATCH 2/2] updated readmi --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7958ab1..8f8616b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ The goal is to provide a minimalistic and fast todo list that is self hostable. - [ ] write a minimal step by step guide to install with nix, - [ ] add some css to make it look nicer - [ ] add htmx to make more agreable without making js manadatory - - [ ] make api to allow usage with native app (a way to get every list that has been modified since date $date belonging from the user in a json or similar format) + - [x] make api to allow usage with native app (a way to get every list that has been modified since date $date belonging from the user in a json or similar format) + - [ ] document api to help create clients ## Version 0.0.3 Simple todo list webapp. Features :