| 1 | -- | The shapes the rest of the app talks in. |
| 2 | module Todo.Model |
| 3 | ( ListId (..) |
| 4 | , TodoId (..) |
| 5 | , Todo (..) |
| 6 | , Filter (..) |
| 7 | , parseFilter |
| 8 | , filterName |
| 9 | , filterPath |
| 10 | , visible |
| 11 | , parseTodoId |
| 12 | , todoIdText |
| 13 | ) where |
| 14 | |
| 15 | import Data.Int (Int64) |
| 16 | import Data.Text (Text) |
| 17 | import Data.Text qualified as T |
| 18 | import Data.Text.Read qualified as T |
| 19 | |
| 20 | -- | One visitor's list. Every query about todos is scoped by one. |
| 21 | newtype ListId = ListId Int64 |
| 22 | deriving (Eq, Show) |
| 23 | |
| 24 | newtype TodoId = TodoId Int64 |
| 25 | deriving (Eq, Show) |
| 26 | |
| 27 | data Todo = Todo |
| 28 | { todoId :: !TodoId |
| 29 | , todoTitle :: !Text |
| 30 | , todoCompleted :: !Bool |
| 31 | } |
| 32 | deriving (Eq, Show) |
| 33 | |
| 34 | data Filter = All | Active | Completed |
| 35 | deriving (Eq, Show, Enum, Bounded) |
| 36 | |
| 37 | -- | The @filter@ form field. Anything missing or unknown means 'All'. |
| 38 | parseFilter :: Maybe Text -> Filter |
| 39 | parseFilter = \case |
| 40 | Just "active" -> Active |
| 41 | Just "completed" -> Completed |
| 42 | _ -> All |
| 43 | |
| 44 | filterName :: Filter -> Text |
| 45 | filterName = \case |
| 46 | All -> "all" |
| 47 | Active -> "active" |
| 48 | Completed -> "completed" |
| 49 | |
| 50 | filterPath :: Filter -> Text |
| 51 | filterPath = \case |
| 52 | All -> "/" |
| 53 | Active -> "/active" |
| 54 | Completed -> "/completed" |
| 55 | |
| 56 | visible :: Filter -> Todo -> Bool |
| 57 | visible = \case |
| 58 | All -> const True |
| 59 | Active -> not . todoCompleted |
| 60 | Completed -> todoCompleted |
| 61 | |
| 62 | -- | A todo id as it appears in a path or a query: plain decimal digits, |
| 63 | -- short enough to fit an 'Int64'. |
| 64 | parseTodoId :: Text -> Maybe TodoId |
| 65 | parseTodoId t |
| 66 | | T.null t || T.length t > 18 || not (T.all (\c -> c >= '0' && c <= '9') t) = Nothing |
| 67 | | otherwise = case T.decimal t of |
| 68 | Right (n, rest) | T.null rest -> Just (TodoId n) |
| 69 | _ -> Nothing |
| 70 | |
| 71 | todoIdText :: TodoId -> Text |
| 72 | todoIdText (TodoId n) = T.pack (show n) |
| 73 | |