chek for overlap when uploading file

This commit is contained in:
Daniel Barlow 2024-11-07 18:59:56 +00:00
parent 4ca505ada1
commit b21eda22ad
5 changed files with 57 additions and 10 deletions

View File

@ -67,12 +67,16 @@ _Do not look below this line_
* need a web server in haskell that
- [done] accepts file upload and parses the gpx file
- serves the data points in some format elm can digest easily
- [done] serves the data points in some format elm can digest easily
* [done] need a database of some kind so the data can be saved
* and boring stuff like auth[zn]
* frontend can get data from backend
* [done] for DX, backend can serve the js files needed by frontend
* [ad hoc] we only have yesod-core, may need other parts as well
* detect and refuse uploads which overlap an existing time frame
(http 409) so that we can script upload-all-the-tracks.
## Postgres
@ -85,3 +89,16 @@ start and stop it when I want to
docker run -p 5432:5432 --name souplesse-postgres -e POSTGRES_USER=souplesse -e POSTGRES_PASSWORD=secret -d postgres
nix-shell -p postgresql --run "psql -h localhost -U souplesse -p 5432"
```
## Sample data
The upload form deliberately doesn't have CSRF (for now, at least) so
that you can chuck a bunch of GPX files at it using curl
```
for i in tmp/Tracks/*.gpx ; do curl --form f1=@$i 'http://localhost:3000/upload'; done
```
This should be safe to do repeatedly because it will refuse upload of
tracks where the database already contains any points in the time
range of the uploaded track

View File

@ -10,14 +10,10 @@ import Control.Monad.Logger (runStderrLoggingT)
import Control.Monad.Trans.Resource (runResourceT)
import Data.ByteString.Lazy as BS
import Data.List as List
import Data.Text (Text, unpack)
-- https://github.com/yesodweb/yesod-cookbook/blob/master/cookbook/Static-file-subsite-Hello-World.md
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import Database.Persist.Postgresql (ConnectionPool, ConnectionString, SqlBackend, runMigration, runSqlPool, withPostgresqlPool)
import Debug.Trace (traceShow)
import Store (fetch, migrateAll, save)
import Text.Read (readMaybe)
import Store
import Track (parseBS)
import Yesod.Core
import Yesod.Form.Fields
@ -124,15 +120,19 @@ instance RenderMessage Souplesse FormMessage where
postUploadR :: Handler Html
postUploadR = do
((result, _), _) <- runFormPost uploadForm
((result, _), _) <- runFormPostNoToken uploadForm
case result of
FormSuccess upload -> do
bs <- fileSourceByteString $ fileInfo upload
case Track.parseBS (fromStrict bs) of
Right points -> do
runDB $ Store.save points
defaultLayout [whamlet|<p>#{List.length points} points - thanks!|]
eitherPoints <- runDB $ Store.save points
case eitherPoints of
Right points' ->
defaultLayout [whamlet|<p>#{List.length points'} points - thanks!|]
Left _ ->
defaultLayout [whamlet|<p>overlap error |]
Left _ ->
defaultLayout
[whamlet|<p>parse error |]

View File

@ -15,8 +15,10 @@
module Store (save, fetch, migrateAll) where
import Control.Exception
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Trans.Reader (ReaderT)
import Data.Maybe (isJust)
import Data.Time.Clock (DiffTime, NominalDiffTime, UTCTime, addUTCTime)
import Database.Persist
import Database.Persist.Class
@ -66,7 +68,20 @@ toPoint entity =
(trkptPower tkp)
(trkptHeartRate tkp)
save p = do mapM_ (insert . fromPoint) p
data OverlapExists = OverlapExists String deriving (Show)
instance Exception OverlapExists
save :: (MonadIO m) => Track -> ReaderT SqlBackend m (Either OverlapExists Track)
save track = do
let start = startTime track
finish = addUTCTime (duration track) (startTime track)
priors <- selectFirst [TrkptTime >. start, TrkptTime <. finish] []
if isJust priors
then return $ Left (OverlapExists "track overlaps with existing data")
else do
mapM_ (insert . fromPoint) track
return $ Right track
fetch :: (MonadIO m) => UTCTime -> NominalDiffTime -> ReaderT SqlBackend m [Point]
fetch start duration = do

View File

@ -10,6 +10,8 @@ module Track
parseFile,
parseBS,
Track.length,
startTime,
duration,
)
where
@ -129,6 +131,18 @@ parse str = do
length :: Track -> Int
length = Data.List.length
startTime :: Track -> UTCTime
startTime (p : ps) = List.foldr (\a b -> min b (time a)) (time p) ps
duration :: Track -> NominalDiffTime
duration track =
case track of
[] -> 0
(p : ps) ->
let start = startTime track
finish = List.foldr (\a b -> max b (time a)) (time p) ps
in diffUTCTime finish start
-- parseFile :: FilePath -> IO [Point]
parseFile name = do
gpx <- Text.XML.readFile def name

View File

@ -79,6 +79,7 @@ executable souplesse
, resourcet
, monad-logger
, time
, transformers
, yesod-core == 1.6.25.1
, yesod-static
, yesod-form