aboutsummaryrefslogtreecommitdiff
path: root/lib/Server
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/Server.hs25
-rw-r--r--lib/Server/Base.hs4
-rw-r--r--lib/Server/Frontend.hs1
-rw-r--r--lib/Server/Frontend/OnboardUnit.hs28
-rw-r--r--lib/Server/Frontend/Routes.hs8
-rw-r--r--lib/Server/Frontend/Ticker.hs40
-rw-r--r--lib/Server/Frontend/Tickets.hs205
-rw-r--r--lib/Server/Frontend/Tracker.hs85
-rw-r--r--lib/Server/GTFS_RT.hs47
-rw-r--r--lib/Server/Ingest.hs134
-rw-r--r--lib/Server/Subscribe.hs66
11 files changed, 445 insertions, 198 deletions
diff --git a/lib/Server.hs b/lib/Server.hs
index 3fc2c5a..4eb101d 100644
--- a/lib/Server.hs
+++ b/lib/Server.hs
@@ -43,8 +43,9 @@ import Servant.Swagger (toSwagger)
import Server.Base (ServerState)
import Server.Frontend (Frontend (..))
import Server.GTFS_RT (gtfsRealtimeServer)
-import Server.Ingest (handleTrackerRegister,
- handleTrainPing, handleWS)
+import Server.Ingest (handleOwntracksMessage,
+ handlePing, handleTrackerRegister,
+ handleWS)
import Server.Subscribe (handleSubscribe)
import Server.Util (Service, runLogging, runService,
serveDirectoryFileServer)
@@ -78,14 +79,15 @@ server
-> Pool SqlBackend
-> ServerConfig
-> Service CompleteAPI
-server gtfs metrics@Metrics{..} subscribers dbpool settings = handleDebugAPI
- :<|> (handleTrackerRegister dbpool
- :<|> handleTrainPing dbpool subscribers settings (throwError err401)
+server gtfs metrics@Metrics{..} subscribers dbpool settings = {- handleDebugAPI
+ :<|> -} (handleTrackerRegister dbpool
+ :<|> handlePing dbpool subscribers settings (throwError err401)
:<|> handleWS dbpool subscribers settings metrics
:<|> handleCurrentTicker
:<|> handleSubscribe dbpool subscribers
:<|> handleDebugState :<|> handleDebugTrain
- :<|> pure (GTFS.gtfsFile gtfs) :<|> gtfsRealtimeServer gtfs dbpool)
+ :<|> pure (GTFS.gtfsFile gtfs) :<|> gtfsRealtimeServer settings gtfs dbpool
+ :<|> owntracksServer)
:<|> handleMetrics
:<|> serveDirectoryFileServer (serverConfigAssets settings)
:<|> pure (unsafePerformIO (toWaiAppPlain (Frontend gtfs dbpool settings)))
@@ -94,8 +96,8 @@ server gtfs metrics@Metrics{..} subscribers dbpool settings = handleDebugAPI
now <- liftIO getCurrentTime
runSql dbpool $ do
tracker <- selectList [TrackerBlocked ==. False, TrackerExpires >=. now] []
- pairs <- forM tracker $ \(Entity token@(TrackerKey uuid) _) -> do
- entities <- selectList [TrainPingToken ==. token] []
+ pairs <- forM tracker $ \(Entity trackerId@(TrackerKey uuid) _) -> do
+ entities <- selectList [PingTrackerId ==. trackerId] []
pure (uuid, fmap entityVal entities)
pure (M.fromList pairs)
handleCurrentTicker = runSql dbpool $ do
@@ -108,11 +110,12 @@ server gtfs metrics@Metrics{..} subscribers dbpool settings = handleDebugAPI
]
handleDebugTrain ticketId = runSql dbpool $ do
trackers <- getTicketTrackers ticketId
- pings <- forM trackers $ \(Entity token _) -> do
- selectList [TrainPingToken ==. token] [] <&> fmap entityVal
+ pings <- forM trackers $ \(Entity trackerId _) -> do
+ selectList [PingTrackerId ==. trackerId] [] <&> fmap entityVal
pure (concat pings)
- handleDebugAPI = pure $ toSwagger (Proxy @API)
+ -- handleDebugAPI = pure $ toSwagger (Proxy @API)
handleMetrics = exportMetricsAsText <&> (decodeUtf8 . toStrict)
+ owntracksServer u d location = handleOwntracksMessage dbpool subscribers settings u d location
getTicketTrackers :: (MonadLogger (t (ResourceT IO)), MonadIO (t (ResourceT IO)))
=> UUID -> ReaderT SqlBackend (t (ResourceT IO)) [Entity Tracker]
diff --git a/lib/Server/Base.hs b/lib/Server/Base.hs
index 14b77ca..17b5b4a 100644
--- a/lib/Server/Base.hs
+++ b/lib/Server/Base.hs
@@ -4,6 +4,6 @@ module Server.Base (ServerState) where
import Control.Concurrent.STM (TQueue, TVar)
import qualified Data.Map as M
import Data.UUID (UUID)
-import Persist (TrainPing)
+import Persist (Ping)
-type ServerState = TVar (M.Map UUID [TQueue (Maybe TrainPing)])
+type ServerState = TVar (M.Map UUID [TQueue (Maybe Ping)])
diff --git a/lib/Server/Frontend.hs b/lib/Server/Frontend.hs
index a9c2f69..9742c3e 100644
--- a/lib/Server/Frontend.hs
+++ b/lib/Server/Frontend.hs
@@ -8,6 +8,7 @@ import Server.Frontend.Routes
import Server.Frontend.SpaceTime
import Server.Frontend.Ticker
import Server.Frontend.Tickets
+import Server.Frontend.Tracker
import Yesod
import Yesod.Auth
diff --git a/lib/Server/Frontend/OnboardUnit.hs b/lib/Server/Frontend/OnboardUnit.hs
index 6a8fe6e..967cb6c 100644
--- a/lib/Server/Frontend/OnboardUnit.hs
+++ b/lib/Server/Frontend/OnboardUnit.hs
@@ -28,7 +28,7 @@ getOnboardTrackerR = do defaultLayout [whamlet|
<section>
<h2>Tracker
- <strong>Token:</strong> <span id="token">
+ <strong>TrackerId:</strong> <span id="trackerId">
<section>
<h2>Status
<p id="status">_{MsgNone}
@@ -44,7 +44,7 @@ getOnboardTrackerR = do defaultLayout [whamlet|
<script>
- var token = null;
+ var trackerId = null;
let euclid = (a,b) => {
let x = a[0]-b[0];
@@ -127,7 +127,7 @@ getOnboardTrackerR = do defaultLayout [whamlet|
if (ws !== undefined && ws.readyState == 1) {
ws.send(JSON.stringify({
- token: token,
+ trackerId: trackerId,
geopos: [ geoloc.coords.latitude, geoloc.coords.longitude ],
timestamp: (new Date()).toISOString()
}));
@@ -144,28 +144,28 @@ getOnboardTrackerR = do defaultLayout [whamlet|
let urlparams = new URLSearchParams(window.location.search);
- token = urlparams.get("token");
+ trackerId = urlparams.get("trackerId");
- if (token === null) {
- token = await (await fetch("/api/tracker/register/", {
+ if (trackerId === null) {
+ trackerId = await (await fetch("/api/tracker/register/", {
method: "POST",
body: JSON.stringify({agent: "tracktrain-website"}),
headers: {"Content-Type": "application/json"}
})).json();
- if (token.error) {
- alert("could not obtain token: \n" + token.msg);
- setStatus("_{MsgTokenFailed}");
+ if (trackerId.error) {
+ alert("could not obtain trackerId: \n" + trackerId.msg);
+ setStatus("_{MsgTrackerIdFailed}");
} else {
- console.log("got token");
- window.location.search = `?token=${token}`;
+ console.log("got trackerId");
+ window.location.search = `?trackerId=${trackerId}`;
}
}
- console.log(token)
+ console.log(trackerId)
- if (token !== null) {
- document.getElementById("token").innerText = token;
+ if (trackerId !== null) {
+ document.getElementById("trackerId").innerText = trackerId;
openWebsocket();
}
}
diff --git a/lib/Server/Frontend/Routes.hs b/lib/Server/Frontend/Routes.hs
index 9245e6a..cf6e342 100644
--- a/lib/Server/Frontend/Routes.hs
+++ b/lib/Server/Frontend/Routes.hs
@@ -19,7 +19,7 @@ import Data.Time.Calendar (Day)
import Data.UUID (UUID)
import Database.Persist.Sql (SqlBackend, runSqlPool)
import qualified GTFS
-import Persist (Token)
+import Persist (TrackerId)
import Text.Blaze.Internal (MarkupM (Empty))
import Yesod
import Yesod.Auth
@@ -45,12 +45,15 @@ mkYesodData "Frontend" [parseRoutes|
/ticket/announce/#UUID AnnounceR POST
/ticket/del-announce/#UUID DelAnnounceR GET
+/trackers TrackersR GET
+/tracker/#Text TrackerViewR GET
+
/ticker/announce TickerAnnounceR POST
/ticker/delete TickerDeleteR POST
/spacetime SpaceTimeDiagramR GET
-/token/block/#Token TokenBlock GET
+/trackerId/block/#TrackerId TrackerIdBlock GET
/gtfs/trips GtfsTripsViewR GET
/gtfs/trip/#GTFS.TripId GtfsTripViewR GET
@@ -149,3 +152,4 @@ instance YesodAuth Frontend where
redirect ("/auth/page/uffd/forward" :: Text)
onLogout = do
clearSession
+
diff --git a/lib/Server/Frontend/Ticker.hs b/lib/Server/Frontend/Ticker.hs
index 861197a..8813200 100644
--- a/lib/Server/Frontend/Ticker.hs
+++ b/lib/Server/Frontend/Ticker.hs
@@ -1,13 +1,15 @@
-{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE QuasiQuotes #-}
module Server.Frontend.Ticker (tickerWidget, postTickerAnnounceR, postTickerDeleteR) where
-import Data.Functor ((<&>))
-import Data.Time (getCurrentTime)
-import Persist (EntityField (TickerAnnouncementArchived),
- TickerAnnouncement (..))
-import Server.Frontend.Routes (FrontendMessage (..), Handler,
- Route (..), Widget)
-import Yesod
+import Data.Functor ((<&>))
+import Data.Time (getCurrentTime)
+import Database.Esqueleto.Experimental hiding ((<&>))
+import Persist (EntityField (TickerAnnouncementArchived),
+ TickerAnnouncement (..))
+import Server.Frontend.Routes (FrontendMessage (..), Handler,
+ Route (..), Widget)
+import Yesod hiding (update, (=.), (==.))
tickerAnnounceForm
@@ -24,7 +26,10 @@ tickerAnnounceForm maybeCurrent = renderDivs $ TickerAnnouncement
tickerWidget :: Handler Html
tickerWidget = do
- current <- runDB $ selectFirst [ TickerAnnouncementArchived ==. False ] []
+ current <- runDB $ selectOne do
+ ann <- from (table @TickerAnnouncement)
+ where_ (ann ^. TickerAnnouncementArchived ==. val False)
+ pure ann
(widget, enctype) <-
generateFormPost (tickerAnnounceForm (current <&> entityVal))
@@ -40,13 +45,19 @@ tickerWidget = do
postTickerAnnounceR :: Handler Html
postTickerAnnounceR = do
- current <- runDB $ selectFirst [ TickerAnnouncementArchived ==. False ] []
+ current <- runDB $ selectOne do
+ ann <- from (table @TickerAnnouncement)
+ where_ (ann ^. TickerAnnouncementArchived ==. val False)
+ pure ann
+
((result, widget), enctype) <-
- runFormPost (tickerAnnounceForm (current <&> entityVal))
+ runFormPost (tickerAnnounceForm (fmap entityVal current))
+
case result of
FormSuccess ann -> do
- runDB $ do
- updateWhere [] [ TickerAnnouncementArchived =. True ]
+ runDB do
+ update \t ->
+ set t [ TickerAnnouncementArchived =. val True ]
insert ann
redirect RootR
_ -> defaultLayout
@@ -59,5 +70,6 @@ postTickerAnnounceR = do
postTickerDeleteR :: Handler Html
postTickerDeleteR = do
- runDB $ updateWhere [] [ TickerAnnouncementArchived =. True ]
+ runDB $ update \t ->
+ set t [TickerAnnouncementArchived =. val True]
redirect RootR
diff --git a/lib/Server/Frontend/Tickets.hs b/lib/Server/Frontend/Tickets.hs
index 9b88a48..76146df 100644
--- a/lib/Server/Frontend/Tickets.hs
+++ b/lib/Server/Frontend/Tickets.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE QuasiQuotes #-}
@@ -10,48 +11,55 @@ module Server.Frontend.Tickets
, getTicketMapViewR
, getDelAnnounceR
, postAnnounceR
- , getTokenBlock
+ , getTrackerIdBlock
) where
import Server.Frontend.Routes
-import Config (ServerConfig (..), UffdConfig (..))
-import Control.Monad (forM, forM_, join)
-import Control.Monad.Extra (maybeM)
-import Control.Monad.IO.Class (MonadIO (liftIO))
-import Data.Coerce (coerce)
-import Data.Function (on, (&))
-import Data.Functor ((<&>))
-import Data.List (lookup, nubBy)
-import Data.List.NonEmpty (nonEmpty)
-import Data.Map (Map)
-import qualified Data.Map as M
-import Data.Maybe (catMaybes, fromJust, isJust)
-import Data.Text (Text)
-import qualified Data.Text as T
-import Data.Time (UTCTime (..), addDays,
- getCurrentTime, utctDay)
-import Data.Time.Calendar (Day)
-import Data.Time.Format.ISO8601 (iso8601Show)
-import Data.UUID (UUID)
-import qualified Data.UUID as UUID
-import qualified Data.Vector as V
-import Extrapolation (Extrapolator (..),
- LinearExtrapolator (..))
-import Fmt ((+|), (|+))
-import GHC.Float (int2Double)
+import Config (ServerConfig (..),
+ UffdConfig (..))
+import Control.Monad (forM, forM_, join)
+import Control.Monad.Extra (maybeM)
+import Control.Monad.IO.Class (MonadIO (liftIO))
+import Data.Coerce (coerce)
+import Data.Function (on, (&))
+import Data.Functor ((<&>))
+import Data.List (lookup, nubBy)
+import Data.List.NonEmpty (nonEmpty)
+import Data.Map (Map)
+import qualified Data.Map as M
+import Data.Maybe (catMaybes, fromJust, isJust)
+import Data.Text (Text)
+import qualified Data.Text as T
+import Data.Time (UTCTime (..), addDays,
+ getCurrentTime, utctDay)
+import Data.Time.Calendar (Day)
+import Data.Time.Format.ISO8601 (iso8601Show)
+import Data.UUID (UUID)
+import qualified Data.UUID as UUID
+import qualified Data.Vector as V
+import Extrapolation (Extrapolator (..),
+ LinearExtrapolator (..))
+import Fmt ((+|), (|+))
+import GHC.Float (int2Double)
import qualified GTFS
-import Numeric (showFFloat)
+import Numeric (showFFloat)
import Persist
-import Server.Frontend.SpaceTime (mkSpaceTimeDiagram,
- mkSpaceTimeDiagramHandler)
-import Server.Frontend.Ticker (tickerWidget)
-import Server.Util (Service, secondsNow)
-import Text.Read (readMaybe)
-import Yesod
+import Server.Frontend.SpaceTime (mkSpaceTimeDiagram,
+ mkSpaceTimeDiagramHandler)
+import Server.Frontend.Ticker (tickerWidget)
+import Server.Util (Service, secondsNow)
+import Text.Read (readMaybe)
+import qualified Yesod
+import Yesod hiding (delete, update, (=.),
+ (==.), (||.))
import Yesod.Auth
-import Yesod.Auth.Uffd (UffdUser (..), uffdClient)
+import Yesod.Auth.Uffd (UffdUser (..), uffdClient)
+import Database.Esqueleto.Experimental (asc, associateJoin, orderBy,
+ where_, (:&) (..), (^.))
+import Database.Esqueleto.Experimental hiding (on, (<&>))
+import qualified Database.Esqueleto.Experimental as E
getTicketsR :: Handler Html
getTicketsR = do
@@ -64,17 +72,23 @@ getTicketsR = do
Just day -> (day, day == today)
Nothing -> (today, True)
- maybeSpaceTime <- mkSpaceTimeDiagramHandler 1 day [ TicketDay ==. day ]
+ maybeSpaceTime <- mkSpaceTimeDiagramHandler 1 day [ TicketDay Yesod.==. day ]
let prevday = (T.pack . iso8601Show . addDays (-1)) day
let nextday = (T.pack . iso8601Show . addDays 1) day
gtfs <- getYesod <&> getGtfs
-- TODO: tickets should have all trip information saved
- tickets <- runDB $ selectList [ TicketDay ==. day ] [ Asc TicketTripName ] >>= mapM (\ticket -> do
- stops <- selectList [ StopTicket ==. entityKey ticket ] []
- startStation <- getJust (stopStation $ entityVal $ head stops)
- pure (ticket, startStation, fmap entityVal stops))
+
+ tickets <- runDB $ E.select do
+ ((ticket :& stop) :& station) <- E.from $
+ (E.table @Ticket `E.InnerJoin` E.table @Stop
+ `E.on` \(ticket :& stop) -> ticket ^. TicketId E.==. stop E.^. StopTicket)
+ `E.InnerJoin` E.table @Station `E.on` \((_ :& stop) :& station) -> stop E.^. StopStation E.==. station ^. StationId
+ where_ (ticket ^. TicketDay E.==. (E.val day))
+ orderBy [asc (ticket ^. TicketTripName)]
+ pure (ticket, (stop, station))
+ & fmap associateJoin
let trips = GTFS.tripsOnDay gtfs day
@@ -98,9 +112,9 @@ $maybe name <- mdisplayname
<section>
<h2>_{MsgTickets}
<ol>
- $forall (Entity (TicketKey ticketId) Ticket{..}, startStation, stops) <- tickets
+ $forall (TicketKey ticketId, (Ticket{..}, stops)) <- M.toList tickets
<li><a href="@{TicketViewR ticketId}">_{MsgTrip} #{ticketTripName}</a>
- : _{Msgdep} #{stopDeparture (head stops)} #{stationName startStation} → #{ticketHeadsign}
+ : _{Msgdep} #{stopDeparture (entityVal (fst (head stops)))} #{stationName (entityVal (snd (head stops)))} → #{ticketHeadsign}
$if null tickets
<li style="text-align: center"><em>(_{MsgNone})</em>
$maybe spaceTime <- maybeSpaceTime
@@ -144,16 +158,18 @@ postGtfsTicketImportR day = do
<&> (\(trip@GTFS.Trip{..}, _) -> V.toList (tripStops <&> GTFS.stopStation))
& concat
& nubBy ((==) `on` GTFS.stationId)
- & mapM (\GTFS.Station{..} -> runDB $ do
- maybeExists <- selectFirst [ StationShortName ==. stationId ] []
- case maybeExists of
+ & mapM (\GTFS.Station{..} -> runDB $ E.selectOne do
+ station <- E.from (E.table @Station)
+ where_ (station ^. StationShortName E.==. E.val stationId)
+ pure station
+ >>= \case
Nothing -> do
key <- insert Station
{ stationGeopos = Geopos (stationLat, stationLon)
, stationShortName = stationId , stationName }
pure (stationId, key)
Just (Entity key _) -> pure (stationId, key))
- <&> M.fromList
+ & fmap M.fromList
selected
<&> (\(trip@GTFS.Trip{..}, day) ->
@@ -190,21 +206,52 @@ getTicketViewR ticketId = do
Ticket{..} <- runDB $ get ticketKey
>>= \case {Nothing -> notFound; Just a -> pure a}
- stops <- runDB $ selectList [StopTicket ==. ticketKey] [] >>= mapM (\stop -> do
- station <- getJust (stopStation (entityVal stop))
- pure (entityVal stop, station))
-
- anns <- runDB $ selectList [ AnnouncementTicket ==. ticketKey ] []
- joins <- runDB $ selectList [ TrackerTicketTicket ==. ticketKey ] []
- <&> fmap (trackerTicketTracker . entityVal)
- trackers <- runDB $ selectList
- ([ TrackerId <-. joins ] ||. [ TrackerCurrentTicket ==. Just ticketKey ])
- [Asc TrackerExpires]
- lastPing <- runDB $ selectFirst [ TrainPingTicket ==. coerce ticketId ] [Desc TrainPingTimestamp]
- anchors <- runDB $ selectList [ TrainAnchorTicket ==. ticketKey ] []
+ stops <- runDB $ select do
+ (stop :& station) <- from $ table @Stop `innerJoin` table @Station
+ `E.on` \(stop :& station) -> stop ^. StopStation ==. station ^. StationId
+ where_ (stop ^. StopTicket ==. val ticketKey)
+ pure (stop, station)
+ -- & fmap associateJoin
+ -- stops <- runDB $ selectList [StopTicket ==. ticketKey] [] >>= mapM (\stop -> do
+ -- station <- getJust (stopStation (entityVal stop))
+ -- pure (entityVal stop, station))
+
+ anns <- runDB $ select do
+ ann <- from (table @Announcement)
+ where_ (ann ^. AnnouncementTicket ==. val ticketKey)
+ pure ann
+
+ -- anns <- runDB $ selectList [ AnnouncementTicket ==. ticketKey ] []
+
+ trackers <- runDB $ select do
+ (tt :& tracker) <- from $
+ table @TrackerTicket `innerJoin` table @Tracker
+ `E.on` \(tt :& tracker) -> tracker ^. TrackerId ==. tt ^. TrackerTicketTracker
+ where_ (tt ^. TrackerTicketTicket ==. val ticketKey
+ ||. tracker ^. TrackerCurrentTicket ==. val (Just ticketKey))
+ pure tracker
+
+ lastPing <- runDB $ selectOne do
+ trainping <- from $ table @Ping
+ where_ (trainping ^. PingTicket ==. val (Just (coerce ticketId)))
+ orderBy [desc (trainping ^. PingTimestamp)]
+ pure trainping
+
+ anchors <- runDB $ select do
+ anchor <- from $ table @TrainAnchor
+ where_ (anchor ^. TrainAnchorTicket ==. val ticketKey)
+ pure anchor
<&> nonEmpty . fmap entityVal
+ -- joins <- runDB $ selectList [ TrackerTicketTicket ==. ticketKey ] []
+ -- <&> fmap (trackerTicketTracker . entityVal)
+ -- trackers <- runDB $ selectList
+ -- ([ TrackerId <-. joins ] ||. [ TrackerCurrentTicket ==. Just ticketKey ])
+ -- [Asc TrackerExpires]
+ -- lastPing <- runDB $ selectFirst [ PingTicket ==. coerce ticketId ] [Desc PingTimestamp]
+ -- anchors <- runDB $ selectList [ TrainAnchorTicket ==. ticketKey ] []
+ -- <&> nonEmpty . fmap entityVal
- spaceTimeMaybe <- mkSpaceTimeDiagramHandler 2 ticketDay [ TicketId ==. coerce ticketId ]
+ spaceTimeMaybe <- mkSpaceTimeDiagramHandler 2 ticketDay [ TicketId Yesod.==. coerce ticketId ]
(widget, enctype) <- generateFormPost (announceForm ticketId)
@@ -220,11 +267,11 @@ getTicketViewR ticketId = do
<section>
<h2>_{MsgLive}
<p><strong>_{MsgLastPing}: </strong>
- $maybe Entity _ TrainPing{..} <- lastPing
- _{MsgTrainPing (latitude trainPingGeopos) (longitude trainPingGeopos) trainPingTimestamp}
+ $maybe Entity _ Ping{..} <- lastPing
+ _{MsgPing (latitude pingGeopos) (longitude pingGeopos) pingTimestamp}
(<a href="/api/debug/pings/#{UUID.toString ticketId}/#{ticketDay}">_{Msgraw}</a>)
$nothing
- <em>(_{MsgNoTrainPing})
+ <em>(_{MsgNoPing})
<p><strong>_{MsgEstimatedDelay}</strong>:
$maybe history <- anchors
$maybe TrainAnchor{..} <- guessAtSeconds history nowSeconds
@@ -235,7 +282,7 @@ getTicketViewR ticketId = do
<section>
<h2>_{MsgStops}
<ol>
- $forall (Stop{..}, Station{..}) <- stops
+ $forall (Entity _ Stop{..}, Entity _ Station{..}) <- stops
<li value="#{stopSequence}"> #{stopArrival} #{stationName}
$maybe history <- anchors
$maybe delay <- guessDelay history (int2Double stopSequence)
@@ -255,9 +302,9 @@ $maybe spaceTime <- spaceTimeMaybe
^{widget}
<button>_{MsgSubmit}
<section>
- <h2>_{MsgTokens}
+ <h2>_{MsgTrackerIds}
<table>
- <tr><th style="width: 20%">_{MsgAgent}</th><th style="width: 50%">_{MsgToken}</th><th>_{MsgExpires}</th><th>_{MsgStatus}</th>
+ <tr><th style="width: 20%">_{MsgAgent}</th><th style="width: 50%">_{MsgTrackerId}</th><th>_{MsgExpires}</th><th>_{MsgStatus}</th>
$if null trackers
<tr><td></td><td style="text-align:center"><em>(_{MsgNone})
$forall Entity (TrackerKey key) Tracker{..} <- trackers
@@ -266,9 +313,9 @@ $maybe spaceTime <- spaceTimeMaybe
<td title="#{key}">#{key}
<td title="#{trackerExpires}">#{trackerExpires}
$if trackerBlocked
- <td title="_{MsgUnblockToken}"><a href="@?{(TokenBlock key, [("unblock", "true")])}">_{MsgUnblockToken}</a>
+ <td title="_{MsgUnblockTrackerId}"><a href="@?{(TrackerIdBlock (TrackerKey key), [("unblock", "true")])}">_{MsgUnblockTrackerId}</a>
$else
- <td title="_{MsgBlockToken}"><a href="@{TokenBlock key}">_{MsgBlockToken}</a>
+ <td title="_{MsgBlockTrackerId}"><a href="@{TrackerIdBlock (TrackerKey key)}">_{MsgBlockTrackerId}</a>
|]
where guessDelay history = fmap trainAnchorDelay . extrapolateAtPosition LinearExtrapolator history
guessAtSeconds = extrapolateAtSeconds LinearExtrapolator
@@ -279,9 +326,12 @@ getTicketMapViewR ticketId = do
Ticket{..} <- runDB $ get (TicketKey ticketId)
>>= \case { Nothing -> notFound ; Just ticket -> pure ticket }
- stops <- runDB $ selectList [StopTicket ==. TicketKey ticketId] [] >>= mapM (\stop -> do
- station <- getJust (stopStation (entityVal stop))
- pure (entityVal stop, station))
+ -- stops <- runDB $ E.select do
+ -- (stop :& station) <- E.from $
+ -- E.table @Stop `E.InnerJoin` E.table @Station
+ -- `E.on` \(stop :& station) -> stop ^. StopStation E.==. station E.^. StationId
+ -- where_ (stop ^. StopTicket E.==. (E.val (TicketKey ticketId)))
+ -- pure (stop, station)
(widget, enctype) <- generateFormPost (announceForm ticketId)
@@ -380,7 +430,9 @@ getDelAnnounceR :: UUID -> Handler Html
getDelAnnounceR uuid = do
ann <- runDB $ do
a <- get (AnnouncementKey uuid)
- delete (AnnouncementKey uuid)
+ delete do
+ ann <- from (table @Announcement)
+ where_ (ann ^. AnnouncementId ==. val (AnnouncementKey uuid))
pure a
case ann of
Nothing -> notFound
@@ -388,13 +440,16 @@ getDelAnnounceR uuid = do
let (TicketKey ticketId) = announcementTicket
in redirect (TicketViewR ticketId)
-getTokenBlock :: Token -> Handler Html
-getTokenBlock token = do
+getTrackerIdBlock :: TrackerId -> Handler Html
+getTrackerIdBlock trackerId = do
YesodRequest{..} <- getRequest
let blocked = lookup "unblock" reqGetParams /= Just "true"
- maybe <- runDB $ do
- update (TrackerKey token) [ TrackerBlocked =. blocked ]
- get (TrackerKey token)
+ maybe <- runDB do
+ update \tracker -> do
+ set tracker [TrackerBlocked =. val blocked]
+ where_ (tracker ^. TrackerId ==. val trackerId)
+ -- Yesod.update (TrackerKey trackerId) [ TrackerBlocked Yesod.=. blocked ]
+ get trackerId
case maybe of
Just r@Tracker{..} -> do
liftIO $ print r
diff --git a/lib/Server/Frontend/Tracker.hs b/lib/Server/Frontend/Tracker.hs
new file mode 100644
index 0000000..e3d88ba
--- /dev/null
+++ b/lib/Server/Frontend/Tracker.hs
@@ -0,0 +1,85 @@
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE QuasiQuotes #-}
+
+module Server.Frontend.Tracker (getTrackerViewR, getTrackersR) where
+import Data.Coerce (coerce)
+import Data.Function ((&))
+import Data.Functor ((<&>))
+import qualified Data.Map as M
+import Data.Text (Text)
+import qualified Data.Text as T
+import Data.Time (getCurrentTime)
+import qualified Data.UUID as UUID
+import Database.Esqueleto.Experimental hiding ((<&>))
+import Persist
+import Server.Frontend.Routes (FrontendMessage (..), Handler,
+ Route (..), Widget)
+import Yesod hiding (update, (=.), (==.))
+
+import OwnTracks.Status
+
+
+getTrackersR :: Handler Html
+getTrackersR = do
+ trackers <- runDB $ select do
+ (t :& p) <- from $
+ (table @Tracker) `LeftOuterJoin` (table @Ping)
+ `on` \(t :& p) -> just (t ^. TrackerId) ==. p ?. PingTrackerId
+ pure (t, p)
+ & fmap associateJoin
+
+ defaultLayout [whamlet|
+ <h1> Trackers
+ <section>
+ <ul>
+ $forall (trackerId, (Tracker{..}, status)) <- M.toList trackers
+ <li><a href="@{TrackerViewR trackerName}">#{trackerName}</a>
+ |]
+
+getTrackerViewR :: Text -> Handler Html
+getTrackerViewR name =
+ runDB (selectOne do
+ tracker <- from (table @Tracker)
+ where_ (tracker ^. TrackerName ==. val name)
+ pure tracker)
+ >>= \case
+ Nothing -> notFound
+ Just (Entity trackerId Tracker{..}) -> do
+
+ (maybeStatus, maybePing) <- runDB $ do
+ status <- selectOne do
+ status <- from (table @TrackerStatus)
+ where_ (status ^. TrackerStatusTracker ==. val trackerId)
+ orderBy [desc $ status ^. TrackerStatusTimestamp]
+ pure status
+ ping <- selectOne do
+ ping <- from (table @Ping)
+ where_ (ping ^. PingTrackerId ==. val trackerId)
+ orderBy [desc $ ping ^. PingTimestamp]
+ pure ping
+ pure (status, ping)
+
+ -- TODO: leaflet map; auto updates?
+ defaultLayout [whamlet|
+ <h1> _{MsgTracker name}
+ <em> (#{trackerId})
+ <section>
+ <h2> _{MsgLastTrackerStatus}
+ $maybe Entity _ TrackerStatus{..} <- maybeStatus
+ LocationPermission: #{show $ statusLocationPermission trackerStatusStatus} <br>
+ BatteryOptimisations: #{show $ statusBatteryOptimizations trackerStatusStatus} <br>
+ Phone in power save mode: #{show $ statusPhonePowerSaveMode trackerStatusStatus}
+ $nothing
+ <em>Status unknown
+ <section>
+ <h2> _{MsgLastTrackerPosition}
+ $maybe Entity _ Ping{..} <- maybePing
+ Position: #{show pingGeopos} <br>
+ Timestamp: #{show pingTimestamp} <br>
+ $maybe ticketId <- pingTicket
+ Ticket: <a href="@{TicketViewR (coerce ticketId)}">#{UUID.toText (coerce ticketId)}</a>
+ $nothing
+ Ticket: (no assigned ticket)
+ $nothing
+ (none)
+ |]
diff --git a/lib/Server/GTFS_RT.hs b/lib/Server/GTFS_RT.hs
index 5ad4b40..4b16a5b 100644
--- a/lib/Server/GTFS_RT.hs
+++ b/lib/Server/GTFS_RT.hs
@@ -8,6 +8,7 @@
module Server.GTFS_RT (gtfsRealtimeServer) where
import API (GtfsRealtimeAPI)
+import Config (ServerConfig (..))
import Control.Lens ((&), (.~))
import Control.Monad (forM)
import Control.Monad.Extra (mapMaybeM)
@@ -45,10 +46,10 @@ import GTFS (Depth (..), GTFS (..),
toSeconds, toUTC, tripsOnDay)
import Persist (Announcement (..),
EntityField (..), Key (..),
- Station (..), Stop (..),
- Ticket (..), Token (..),
- Tracker (..), TrainAnchor (..),
- TrainPing (..), latitude,
+ Ping (..), Station (..),
+ Stop (..), Ticket (..),
+ Tracker (..), TrackerId (..),
+ TrainAnchor (..), latitude,
longitude, runSql)
import qualified Proto.GtfsRealtime as RT
import qualified Proto.GtfsRealtime_Fields as RT
@@ -69,17 +70,22 @@ toStupidDate date =
toStupidTime :: Num i => UTCTime -> i
toStupidTime = fromIntegral . systemSeconds . utcToSystemTime
-gtfsRealtimeServer :: GTFS -> Pool SqlBackend -> Service GtfsRealtimeAPI
-gtfsRealtimeServer gtfs@GTFS{..} dbpool =
+gtfsRealtimeServer :: ServerConfig -> GTFS -> Pool SqlBackend -> Service GtfsRealtimeAPI
+gtfsRealtimeServer settings@ServerConfig{..} gtfs@GTFS{..} dbpool =
handleServiceAlerts :<|> handleTripUpdates :<|> handleVehiclePositions
+
where
- handleServiceAlerts = runSql dbpool $ do
+
+ -- return an empty message if we're in silent mode & not force=yes
+ doNothingIfSilent m force =
+ if serverConfigBeSilent && not force then defFeedMessage mempty
+ else m >>= defFeedMessage
+
+ handleServiceAlerts = doNothingIfSilent $ runSql dbpool $ do
announcements <- selectList [] []
- alerts <- forM announcements $ \(Entity (AnnouncementKey uuid) announcement@Announcement{..}) -> do
+ forM announcements $ \(Entity (AnnouncementKey uuid) announcement@Announcement{..}) -> do
ticket <- getJust announcementTicket
pure $ mkAlert uuid announcement ticket
- defFeedMessage alerts
-
where
mkAlert :: UUID.UUID -> Announcement -> Ticket -> RT.FeedEntity
mkAlert uuid Announcement{..} Ticket{..} =
@@ -95,7 +101,7 @@ gtfsRealtimeServer gtfs@GTFS{..} dbpool =
& RT.descriptionText .~ monolingual "de" announcementMessage
)
- handleTripUpdates = runSql dbpool $ do
+ handleTripUpdates = doNothingIfSilent $ runSql dbpool $ do
now <- liftIO getCurrentTime
let today = utctDay now
nowSeconds <- secondsNow today
@@ -154,25 +160,24 @@ gtfsRealtimeServer gtfs@GTFS{..} dbpool =
& RT.uncertainty .~ 60
)
& RT.scheduleRelationship .~ RT.TripUpdate'StopTimeUpdate'SCHEDULED
+ pure (catMaybes tripUpdates)
- defFeedMessage (catMaybes tripUpdates)
-
- handleVehiclePositions = runSql dbpool $ do
+ handleVehiclePositions = doNothingIfSilent $ runSql dbpool $ do
ticket <- selectList [TicketCompleted ==. False] []
-- TODO: reimplement this (since trainpings no longer reference tickets it's gone for now)
-- positions <- forM ticket $ \(Entity key ticket) -> do
- -- selectFirst [TrainPingTicket ==. key] [Desc TrainPingTimestamp] >>= \case
+ -- selectFirst [PingTicket ==. key] [Desc PingTimestamp] >>= \case
-- Nothing -> pure Nothing
-- Just lastPing ->
-- pure (Just $ mkPosition (lastPing, ticket))
- defFeedMessage [] -- (catMaybes positions)
+ pure [] -- (catMaybes positions)
where
- mkPosition :: (Entity TrainPing, Ticket) -> RT.FeedEntity
- mkPosition (Entity key TrainPing{..}, Ticket{..}) = defMessage
+ mkPosition :: (Entity Ping, Ticket) -> RT.FeedEntity
+ mkPosition (Entity key Ping{..}, Ticket{..}) = defMessage
& RT.id .~ T.pack (show key)
& RT.vehicle .~ (defMessage
& RT.trip .~ defTripDescriptor ticketTripName Nothing Nothing
@@ -181,11 +186,11 @@ gtfsRealtimeServer gtfs@GTFS{..} dbpool =
Just trainset -> Just $ defMessage
& RT.label .~ trainset
& RT.position .~ (defMessage
- & RT.latitude .~ double2Float (latitude trainPingGeopos)
- & RT.longitude .~ double2Float (longitude trainPingGeopos)
+ & RT.latitude .~ double2Float (latitude pingGeopos)
+ & RT.longitude .~ double2Float (longitude pingGeopos)
)
-- TODO: should probably give currentStopSequence/stopId here as well
- & RT.timestamp .~ toStupidTime trainPingTimestamp
+ & RT.timestamp .~ toStupidTime pingTimestamp
)
diff --git a/lib/Server/Ingest.hs b/lib/Server/Ingest.hs
index 959a4c6..8e122a7 100644
--- a/lib/Server/Ingest.hs
+++ b/lib/Server/Ingest.hs
@@ -1,7 +1,7 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards #-}
-module Server.Ingest (handleTrackerRegister, handleTrainPing, handleWS) where
+module Server.Ingest (handleTrackerRegister, handlePing, handleWS, handleOwntracksMessage) where
import API (Metrics (..),
RegisterJson (..),
SentPing (..))
@@ -13,7 +13,8 @@ import Control.Monad.Catch (handle)
import Control.Monad.Extra (ifM, mapMaybeM, whenJust,
whenJustM)
import Control.Monad.IO.Class (MonadIO (liftIO))
-import Control.Monad.Logger (LoggingT, logInfoN,
+import Control.Monad.Logger (LoggingT, logDebugN,
+ logErrorN, logInfoN,
logWarnN)
import Control.Monad.Reader (ReaderT)
import qualified Data.Aeson as A
@@ -36,7 +37,8 @@ import Fmt ((+|), (|+))
import qualified GTFS
import qualified Network.WebSockets as WS
import Persist
-import Servant (err400, throwError)
+import Servant (err400, err401,
+ throwError)
import Servant.Server (Handler)
import Server.Util (ServiceM, getTzseries,
utcToSeconds)
@@ -44,46 +46,49 @@ import Server.Util (ServiceM, getTzseries,
import Config (LoggingConfig,
ServerConfig (..))
import Control.Exception (throw)
-import Control.Monad.Logger.CallStack (logErrorN)
import Data.ByteString (ByteString)
import Data.ByteString.Lazy (toStrict)
import Data.Foldable (find, minimumBy)
import Data.Function (on, (&))
+import Data.Maybe (fromJust)
import qualified Data.Text as T
import Data.Time.LocalTime.TimeZone.Series (TimeZoneSeries)
import qualified Data.UUID as UUID
+import Database.Esqueleto.Experimental (from, selectOne, table,
+ val, where_, (^.))
+import qualified Database.Esqueleto.Experimental as E
import Extrapolation (Extrapolator (..),
LinearExtrapolator (..),
euclid)
import GHC.Generics (Generic)
import GTFS (seconds2Double)
+import OwnTracks hiding (Ping)
import Prometheus (decGauge, incGauge)
import Server.Base (ServerState)
-
handleTrackerRegister
:: Pool SqlBackend
-> RegisterJson
- -> ServiceM Token
+ -> ServiceM TrackerId
handleTrackerRegister dbpool RegisterJson{..} = do
today <- liftIO getCurrentTime <&> utctDay
expires <- liftIO $ getCurrentTime <&> addUTCTime validityPeriod
runSql dbpool $ do
- TrackerKey tracker <- insert (Tracker expires False registerAgent Nothing)
- pure tracker
+ TrackerKey tracker <- insert (Tracker "dummy" expires False registerAgent Nothing)
+ pure (coerce tracker)
where
validityPeriod :: NominalDiffTime
validityPeriod = nominalDay
-handleTrainPing
+handlePing
:: Pool SqlBackend
-> ServerState
-> ServerConfig
-> LoggingT (ReaderT LoggingConfig Handler) a
-> SentPing
-> LoggingT (ReaderT LoggingConfig Handler) (Maybe TrainAnchor)
-handleTrainPing dbpool subscribers cfg onError ping@SentPing{..} =
- isTokenValid dbpool sentPingToken >>= \case
+handlePing dbpool subscribers cfg onError ping@SentPing{..} =
+ isTrackerIdValid dbpool sentPingTrackerId >>= \case
Nothing -> onError >> pure Nothing
Just tracker@Tracker{..} -> do
@@ -107,13 +112,76 @@ handleTrainPing dbpool subscribers cfg onError ping@SentPing{..} =
Nothing -> runSql dbpool (guessTicketFromPing cfg ping) >>= \case
Just ticketId -> pure ticketId
Nothing -> do
- logWarnN $ "Tracker "+|UUID.toString (coerce sentPingToken)|+
+ logWarnN $ "Tracker "+|UUID.toString (coerce sentPingTrackerId)|+
" sent a ping, but no trips are running today."
throwError err400
-
runSql dbpool $ insertSentPing subscribers cfg ping tracker ticketId
+
+handleOwntracksMessage
+ :: Pool SqlBackend
+ -> ServerState
+ -> ServerConfig
+ -> Maybe Text
+ -> Maybe Text
+ -> Message
+ -> LoggingT (ReaderT LoggingConfig Handler) ()
+handleOwntracksMessage dbpool subscribers cfg maybeUser device msg = do
+ user <- case maybeUser of
+ Just user -> pure user
+ Nothing -> throwError err401
+
+ -- TODO: maybe get the basic json here, and put it into a log-msg table?
+
+ logDebugN $ "received msg: "+|show msg|+"."
+
+ Entity trackerId tracker@Tracker{..} <- runSql dbpool $ (selectOne do
+ tracker <- from (table @Tracker)
+ where_ (tracker ^. TrackerName E.==. val user)
+ pure tracker)
+ >>= \case
+ Just tracker -> pure tracker
+ Nothing -> throw err401
+
+ case msg of
+ MsgStatus status -> do
+ now <- liftIO getCurrentTime
+ logInfoN $ "received status msg: "+|show status|+""
+ runSql dbpool $ insert_ $ TrackerStatus trackerId now status
+ MsgLocation Location{..} -> do
+ let ping = SentPing
+ { sentPingTrackerId = trackerId
+ , sentPingGeopos = Geopos (locationLatitude, locationLongitude)
+ , sentPingTimestamp = locationTimestamp
+ }
+
+ maybeTicketId <- case trackerCurrentTicket of
+ -- if the tracker is not associated with a ticket, it is probably new
+ -- & should be auto-associated with the most fitting current ticket
+ Nothing -> runSql dbpool (guessTicketFromPing cfg ping) >>= \case
+ Just ticketId -> pure (Just ticketId)
+ Nothing -> do
+ -- unfortunately, cannot really communicate anything useful back?
+ logWarnN $ "Owntracks user "+|user|+
+ " sent a ping, but no trips are running today."
+ pure Nothing
+
+ case maybeTicketId of
+ Nothing -> do
+ runSql dbpool $ insert $ Ping
+ { pingTicket = Nothing
+ , pingTrackerId = trackerId
+ , pingGeopos = Geopos (locationLatitude, locationLongitude)
+ , pingTimestamp = locationTimestamp
+ , pingSequence = Nothing
+ }
+ pure ()
+ Just ticketId -> do
+ runSql dbpool $ insertSentPing subscribers cfg undefined tracker ticketId
+ pure ()
+
+
insertSentPing
:: ServerState
-> ServerConfig
@@ -140,9 +208,9 @@ insertSentPing subscribers cfg ping@SentPing{..} tracker@Tracker{..} ticketId =
maybeReassign <- selectFirst
- [ TrainPingTicket ==. ticketId ]
- [ Desc TrainPingTimestamp ]
- <&> find (\ping -> trainPingSequence (entityVal ping) > trainAnchorSequence anchor)
+ [ PingTicket ==. Just ticketId, PingSequence !=. Nothing ]
+ [ Desc PingTimestamp ]
+ <&> find (\ping -> fromJust (pingSequence (entityVal ping)) > trainAnchorSequence anchor)
>> guessTicketFromPing cfg ping
<&> find (/= ticketId)
@@ -154,19 +222,19 @@ insertSentPing subscribers cfg ping@SentPing{..} tracker@Tracker{..} ticketId =
case maybeReassign of
Just newTicketId -> do
- update sentPingToken
+ update sentPingTrackerId
[TrackerCurrentTicket =. Just newTicketId ]
- logInfoN $ "tracker "+|UUID.toText (coerce sentPingToken)|+
+ logInfoN $ "tracker "+|UUID.toText (coerce sentPingTrackerId)|+
"has switched direction, and was reassigned to ticket "
+|UUID.toText (coerce newTicketId)|+"."
insertSentPing subscribers cfg ping tracker newTicketId
Nothing -> do
- let trackedPing = TrainPing
- { trainPingToken = sentPingToken
- , trainPingGeopos = sentPingGeopos
- , trainPingTimestamp = sentPingTimestamp
- , trainPingSequence = trainAnchorSequence anchor
- , trainPingTicket = ticketId
+ let trackedPing = Ping
+ { pingTrackerId = sentPingTrackerId
+ , pingGeopos = sentPingGeopos
+ , pingTimestamp = sentPingTimestamp
+ , pingSequence = Just (trainAnchorSequence anchor)
+ , pingTicket = Just ticketId
}
insert trackedPing
@@ -182,11 +250,11 @@ insertSentPing subscribers cfg ping@SentPing{..} tracker@Tracker{..} ticketId =
& (\(stop, _, _) -> stopSequence stop)
& fromIntegral
when (trainAnchorSequence anchor + 0.1 >= maxSequence) $ do
- update sentPingToken
+ update sentPingTrackerId
[TrackerCurrentTicket =. Nothing]
update ticketId
[TicketCompleted =. True]
- logInfoN $ "Tracker "+|UUID.toString (coerce sentPingToken)|+
+ logInfoN $ "Tracker "+|UUID.toString (coerce sentPingTrackerId)|+
" has completed ticket "+|UUID.toString (coerce ticketId)|+
" (trip "+|ticketTripName|+")"
@@ -214,9 +282,9 @@ handleWS dbpool subscribers cfg Metrics{..} conn = do
liftIO $ WS.sendClose conn (C8.pack err)
-- TODO: send a close msg (Nothing) to the subscribed queues? decGauge metricsWSGauge
Right ping -> do
- -- if invalid token, send a "polite" close request. Note that the client may
+ -- if invalid trackerId, send a "polite" close request. Note that the client may
-- ignore this and continue sending messages, which will continue to be handled.
- handleTrainPing dbpool subscribers cfg (liftIO $ WS.sendClose conn ("" :: ByteString)) ping >>= \case
+ handlePing dbpool subscribers cfg (liftIO $ WS.sendClose conn ("" :: ByteString)) ping >>= \case
Just anchor -> liftIO $ WS.sendTextData conn (A.encode anchor)
Nothing -> pure ()
@@ -245,11 +313,11 @@ guessTicketFromPing cfg SentPing{..} = do
in smallestDistance))
logInfoN
- $ "Tracker "+|UUID.toString (coerce sentPingToken)|+
+ $ "Tracker "+|UUID.toString (coerce sentPingTrackerId)|+
" is now handling ticket "+|UUID.toString (coerce (entityKey closestTicket))|+
" (trip "+|ticketTripName (entityVal closestTicket)|+")."
- update sentPingToken
+ update sentPingTrackerId
[TrackerCurrentTicket =. Just (entityKey closestTicket)]
pure (Just (entityKey closestTicket))
@@ -260,9 +328,9 @@ spaceAndTimeDiff (pos1, time1) (pos2, time2) =
where spaceDistance = euclid pos1 pos2
timeDiff = time1 - time2
--- TODO: proper debug logging for expired tokens
-isTokenValid :: Pool SqlBackend -> TrackerId -> ServiceM (Maybe Tracker)
-isTokenValid dbpool token = runSql dbpool $ get token >>= \case
+-- TODO: proper debug logging for expired trackerIds
+isTrackerIdValid :: Pool SqlBackend -> TrackerId -> ServiceM (Maybe Tracker)
+isTrackerIdValid dbpool trackerId = runSql dbpool $ get trackerId >>= \case
Just tracker | not (trackerBlocked tracker) -> do
ifM (hasExpired (trackerExpires tracker))
(pure Nothing)
diff --git a/lib/Server/Subscribe.hs b/lib/Server/Subscribe.hs
index 831f4c9..86b67a6 100644
--- a/lib/Server/Subscribe.hs
+++ b/lib/Server/Subscribe.hs
@@ -1,27 +1,26 @@
+{-# LANGUAGE BlockArguments #-}
module Server.Subscribe where
-import Conduit (MonadIO (..))
-import Control.Concurrent.STM (atomically, newTQueue, readTQueue,
- readTVar, writeTVar)
-import Control.Exception (handle)
-import Control.Monad.Extra (forever, whenJust)
-import qualified Data.Aeson as A
-import qualified Data.ByteString.Char8 as C8
-import Data.Coerce (coerce)
-import Data.Functor ((<&>))
-import Data.Map (Map)
-import qualified Data.Map as M
+import Conduit (MonadIO (..))
+import Control.Concurrent.STM (atomically, newTQueue,
+ readTQueue, readTVar,
+ writeTVar)
+import Control.Exception (handle)
+import Control.Monad.Extra (forever, whenJust)
+import qualified Data.Aeson as A
+import qualified Data.ByteString.Char8 as C8
+import Data.Coerce (coerce)
+import Data.Functor ((<&>))
+import Data.Map (Map)
+import qualified Data.Map as M
import Data.Pool
-import Data.UUID (UUID)
-import Database.Persist (Entity (entityKey), SelectOpt (Desc),
- entityVal, selectFirst, selectList,
- (<-.), (==.), (||.))
-import Database.Persist.Sql (SqlBackend)
-import qualified Network.WebSockets as WS
+import Data.UUID (UUID)
+import Database.Esqueleto.Experimental hiding ((<&>))
+import Database.Persist.Sql (SqlBackend)
+import qualified Network.WebSockets as WS
import Persist
-import Server.Base (ServerState)
-import Server.Util (ServiceM)
-
+import Server.Base (ServerState)
+import Server.Util (ServiceM)
handleSubscribe
:: Pool SqlBackend
@@ -38,8 +37,11 @@ handleSubscribe dbpool subscribers (ticketId :: UUID) conn = liftIO $ WS.withPin
pure queue
-- send most recent ping, if any (so we won't have to wait for movement)
- runSqlWithoutLog dbpool
- (selectFirst [TrainPingTicket ==. coerce ticketId] [Desc TrainPingTimestamp])
+ runSqlWithoutLog dbpool (selectOne do
+ ping <- from (table @Ping)
+ where_ (ping ^. PingTicket ==. val (Just (coerce ticketId)))
+ orderBy [desc (ping ^. PingTimestamp)]
+ pure ping)
<&> fmap entityVal
>>= flip whenJust (WS.sendTextData conn . A.encode)
@@ -57,7 +59,19 @@ handleSubscribe dbpool subscribers (ticketId :: UUID) conn = liftIO $ WS.withPin
-- getTicketTrackers :: (MonadLogger (t (ResourceT IO)), MonadIO (t (ResourceT IO)))
-- => UUID -> ReaderT SqlBackend (t (ResourceT IO)) [Entity Tracker]
-getTicketTrackers ticketId = do
- joins <- selectList [TrackerTicketTicket ==. TicketKey ticketId] []
- <&> fmap (trackerTicketTracker . entityVal)
- selectList ([TrackerId <-. joins] ||. [TrackerCurrentTicket ==. Just (TicketKey ticketId)]) []
+getTicketTrackers ticketId = select do
+ (tracker :& trackerticket) <- from $
+ table @Tracker
+ `innerJoin`
+ table @TrackerTicket
+ `on` \(tr :& ti) -> tr ^. TrackerId ==. ti ^. TrackerTicketTracker
+
+ where_ $
+ tracker ^. TrackerCurrentTicket ==. val (Just (TicketKey ticketId))
+ ||. trackerticket ^. TrackerTicketTicket ==. val (TicketKey ticketId)
+
+ pure tracker
+
+ -- joins <- selectList [TrackerTicketTicket ==. TicketKey ticketId] []
+ -- <&> fmap (trackerTicketTracker . entityVal)
+ -- selectList ([TrackerId <-. joins] ||. [TrackerCurrentTicket ==. Just (TicketKey ticketId)]) []