Initial commit
This commit is contained in:
commit
6fa9746f36
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
dist-newstyle/
|
5
CHANGELOG.md
Normal file
5
CHANGELOG.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Revision history for soostone
|
||||||
|
|
||||||
|
## 0.1.0.0 -- YYYY-mm-dd
|
||||||
|
|
||||||
|
* First version. Released on an unsuspecting world.
|
29
README.md
Normal file
29
README.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Soostone
|
||||||
|
|
||||||
|
## Start
|
||||||
|
|
||||||
|
First create tables:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
soostone create-tables
|
||||||
|
```
|
||||||
|
|
||||||
|
Then start backend:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
soostone run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Request examples
|
||||||
|
|
||||||
|
### Insert
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -v -H 'Content-Type: application/json' -X POST --data '"abc"' http://localhost:9000/api/v1/input
|
||||||
|
```
|
||||||
|
|
||||||
|
### Query
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl -v -X GET 'http://localhost:9000/api/v1/query?key=abc'
|
||||||
|
```
|
112
app/Main.hs
Normal file
112
app/Main.hs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
|
||||||
|
module Main where
|
||||||
|
|
||||||
|
import Soostone ( app, middleWare, AppState(AppState, sqliteFile) )
|
||||||
|
|
||||||
|
import Control.Monad ( void, when )
|
||||||
|
import Data.Version (showVersion)
|
||||||
|
import Database.SQLite.Simple ( execute_, open, Connection )
|
||||||
|
import Network.Wai.Handler.Warp ( run )
|
||||||
|
import Options.Applicative
|
||||||
|
( fullDesc,
|
||||||
|
execParser,
|
||||||
|
progDesc,
|
||||||
|
helper,
|
||||||
|
(<**>),
|
||||||
|
info,
|
||||||
|
command,
|
||||||
|
subparser,
|
||||||
|
strOption,
|
||||||
|
value,
|
||||||
|
showDefault,
|
||||||
|
metavar,
|
||||||
|
eitherReader,
|
||||||
|
option,
|
||||||
|
hidden,
|
||||||
|
help,
|
||||||
|
long,
|
||||||
|
infoOption,
|
||||||
|
Parser )
|
||||||
|
import Paths_soostone ( version )
|
||||||
|
import Prometheus ( register )
|
||||||
|
import Prometheus.Metric.GHC ( ghcMetrics )
|
||||||
|
import Prometheus.Metric.Proc ( procMetrics )
|
||||||
|
import Text.Read ( readEither )
|
||||||
|
|
||||||
|
|
||||||
|
versioner :: Parser (a -> a)
|
||||||
|
versioner = infoOption (showVersion version) (long "version" <> help "Show version" <> hidden)
|
||||||
|
|
||||||
|
data Command = Run ServerConfig
|
||||||
|
| Create ServerConfig
|
||||||
|
|
||||||
|
data Config = Config {
|
||||||
|
serverCommand :: Command
|
||||||
|
}
|
||||||
|
|
||||||
|
parseConfig :: Parser Config
|
||||||
|
parseConfig = Config <$> parseCommands
|
||||||
|
|
||||||
|
data ServerConfig = ServerConfig {
|
||||||
|
serverPort :: Int
|
||||||
|
, sqliteDB :: FilePath
|
||||||
|
}
|
||||||
|
|
||||||
|
parseServerConf :: Parser ServerConfig
|
||||||
|
parseServerConf = ServerConfig
|
||||||
|
<$> option
|
||||||
|
(eitherReader (readEither @Int))
|
||||||
|
(long "server-port" <> metavar "SERVER_PORT" <> help
|
||||||
|
"Port to use for the REST server"
|
||||||
|
<> showDefault
|
||||||
|
<> value 9000
|
||||||
|
)
|
||||||
|
<*> strOption
|
||||||
|
( long "sqlite-db"
|
||||||
|
<> metavar "SQLITE_DB"
|
||||||
|
<> help "SQLite relation/database"
|
||||||
|
<> showDefault
|
||||||
|
<> value "soostone.sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
parseCommands :: Parser Command
|
||||||
|
parseCommands = subparser $
|
||||||
|
command
|
||||||
|
"run"
|
||||||
|
(info
|
||||||
|
(Run <$> parseServerConf <**> helper)
|
||||||
|
( progDesc "Run the REST API webserver"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
<>
|
||||||
|
command
|
||||||
|
"create-tables"
|
||||||
|
(info
|
||||||
|
(Create <$> parseServerConf <**> helper)
|
||||||
|
( progDesc "Create the database"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
Config{ .. } <-
|
||||||
|
execParser (info (parseConfig <**> helper <**> versioner) (fullDesc <> progDesc "Soostone"))
|
||||||
|
|
||||||
|
case serverCommand of
|
||||||
|
Run ServerConfig{..} -> do
|
||||||
|
when (sqliteDB == ":memory:") $ fail "In-memory DB is not supported!"
|
||||||
|
let appState = AppState { sqliteFile = sqliteDB }
|
||||||
|
void $ register ghcMetrics
|
||||||
|
void $ register procMetrics
|
||||||
|
run serverPort (middleWare $ app appState)
|
||||||
|
Create ServerConfig{..} -> do
|
||||||
|
when (sqliteDB == ":memory:") $ fail "In-memory DB is not supported!"
|
||||||
|
con <- open sqliteDB
|
||||||
|
createTables con
|
||||||
|
where
|
||||||
|
createTables :: Connection -> IO ()
|
||||||
|
createTables con = do
|
||||||
|
execute_ con "CREATE TABLE IF NOT EXISTS keys (id INTEGER PRIMARY KEY, key TEXT, count INTEGER)"
|
8
cabal.project
Normal file
8
cabal.project
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
packages: ./soostone.cabal
|
||||||
|
|
||||||
|
with-compiler: ghc-8.10.7
|
||||||
|
|
||||||
|
optional-packages: ./vendored/*/*.cabal
|
||||||
|
|
||||||
|
allow-newer: text
|
||||||
|
|
216
cabal.project.freeze
Normal file
216
cabal.project.freeze
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
active-repositories: hackage.haskell.org:merge
|
||||||
|
constraints: any.Cabal ==3.6.3.0,
|
||||||
|
Cabal -bundled-binary-generic,
|
||||||
|
any.HUnit ==1.6.2.0,
|
||||||
|
any.OneTuple ==0.3.1,
|
||||||
|
any.Only ==0.1,
|
||||||
|
any.QuickCheck ==2.14.2,
|
||||||
|
QuickCheck -old-random +templatehaskell,
|
||||||
|
any.StateVar ==1.2.2,
|
||||||
|
any.aeson ==2.0.3.0,
|
||||||
|
aeson -cffi +ordered-keymap,
|
||||||
|
any.ansi-terminal ==0.11.1,
|
||||||
|
ansi-terminal -example,
|
||||||
|
any.ansi-wl-pprint ==0.6.9,
|
||||||
|
ansi-wl-pprint -example,
|
||||||
|
any.appar ==0.1.8,
|
||||||
|
any.array ==0.5.4.0,
|
||||||
|
any.asn1-encoding ==0.9.6,
|
||||||
|
any.asn1-parse ==0.9.5,
|
||||||
|
any.asn1-types ==0.3.4,
|
||||||
|
any.assoc ==1.0.2,
|
||||||
|
any.async ==2.2.4,
|
||||||
|
async -bench,
|
||||||
|
any.atomic-primops ==0.8.4,
|
||||||
|
atomic-primops -debug,
|
||||||
|
any.attoparsec ==0.14.4,
|
||||||
|
attoparsec -developer,
|
||||||
|
any.attoparsec-iso8601 ==1.0.2.1,
|
||||||
|
attoparsec-iso8601 -developer -fast,
|
||||||
|
any.auto-update ==0.1.6,
|
||||||
|
any.base ==4.14.3.0,
|
||||||
|
any.base-compat ==0.12.1,
|
||||||
|
any.base-compat-batteries ==0.12.1,
|
||||||
|
any.base-orphans ==0.8.6,
|
||||||
|
any.base64-bytestring ==1.2.1.0,
|
||||||
|
any.basement ==0.0.14,
|
||||||
|
any.bifunctors ==5.5.11,
|
||||||
|
bifunctors +semigroups +tagged,
|
||||||
|
any.binary ==0.8.9.0,
|
||||||
|
any.blaze-builder ==0.4.2.2,
|
||||||
|
any.blaze-html ==0.9.1.2,
|
||||||
|
any.blaze-markup ==0.8.2.8,
|
||||||
|
any.blaze-textual ==0.2.2.1,
|
||||||
|
blaze-textual -developer -integer-simple +native,
|
||||||
|
any.boring ==0.2,
|
||||||
|
boring +tagged,
|
||||||
|
any.bsb-http-chunked ==0.0.0.4,
|
||||||
|
any.byteorder ==1.0.4,
|
||||||
|
any.bytestring ==0.11.3.0,
|
||||||
|
any.cabal-doctest ==1.0.9,
|
||||||
|
any.call-stack ==0.4.0,
|
||||||
|
any.case-insensitive ==1.2.1.0,
|
||||||
|
any.clock ==0.8.3,
|
||||||
|
clock -llvm,
|
||||||
|
any.colour ==2.3.6,
|
||||||
|
any.comonad ==5.0.8,
|
||||||
|
comonad +containers +distributive +indexed-traversable,
|
||||||
|
any.constraints ==0.13.3,
|
||||||
|
any.containers ==0.6.5.1,
|
||||||
|
any.contravariant ==1.5.5,
|
||||||
|
contravariant +semigroups +statevar +tagged,
|
||||||
|
any.cookie ==0.4.5,
|
||||||
|
any.cryptonite ==0.30,
|
||||||
|
cryptonite -check_alignment +integer-gmp -old_toolchain_inliner +support_aesni +support_deepseq -support_pclmuldq +support_rdrand -support_sse +use_target_attributes,
|
||||||
|
any.data-default ==0.7.1.1,
|
||||||
|
any.data-default-class ==0.1.2.0,
|
||||||
|
any.data-default-instances-containers ==0.0.1,
|
||||||
|
any.data-default-instances-dlist ==0.0.1,
|
||||||
|
any.data-default-instances-old-locale ==0.0.1,
|
||||||
|
any.data-fix ==0.3.2,
|
||||||
|
any.data-sketches ==0.3.1.0,
|
||||||
|
any.data-sketches-core ==0.1.0.0,
|
||||||
|
any.dec ==0.0.4,
|
||||||
|
any.deepseq ==1.4.4.0,
|
||||||
|
any.direct-sqlite ==2.3.26,
|
||||||
|
direct-sqlite +fulltextsearch +haveusleep +json1 -systemlib +urifilenames,
|
||||||
|
any.directory ==1.3.7.0,
|
||||||
|
any.distributive ==0.6.2.1,
|
||||||
|
distributive +semigroups +tagged,
|
||||||
|
any.dlist ==1.0,
|
||||||
|
dlist -werror,
|
||||||
|
any.easy-file ==0.2.2,
|
||||||
|
any.exceptions ==0.10.4,
|
||||||
|
any.fast-logger ==3.1.1,
|
||||||
|
any.file-embed ==0.0.15.0,
|
||||||
|
any.filepath ==1.4.2.1,
|
||||||
|
any.filtrable ==0.1.6.0,
|
||||||
|
filtrable +containers,
|
||||||
|
any.ghc-boot-th ==8.10.7,
|
||||||
|
any.ghc-prim ==0.6.1,
|
||||||
|
any.hashable ==1.4.0.2,
|
||||||
|
hashable +containers +integer-gmp -random-initial-seed,
|
||||||
|
any.hourglass ==0.2.12,
|
||||||
|
any.hsc2hs ==0.68.8,
|
||||||
|
hsc2hs -in-ghc-tree,
|
||||||
|
any.http-api-data ==0.4.3,
|
||||||
|
http-api-data -use-text-show,
|
||||||
|
any.http-date ==0.0.11,
|
||||||
|
any.http-media ==0.8.0.0,
|
||||||
|
any.http-types ==0.12.3,
|
||||||
|
any.http2 ==3.0.3,
|
||||||
|
http2 -devel -doc -h2spec,
|
||||||
|
any.indexed-traversable ==0.1.2,
|
||||||
|
any.indexed-traversable-instances ==0.1.1,
|
||||||
|
any.integer-gmp ==1.0.3.0,
|
||||||
|
any.integer-logarithms ==1.0.3.1,
|
||||||
|
integer-logarithms -check-bounds +integer-gmp,
|
||||||
|
any.iproute ==1.7.12,
|
||||||
|
any.math-functions ==0.3.4.2,
|
||||||
|
math-functions +system-erf +system-expm1,
|
||||||
|
any.memory ==0.17.0,
|
||||||
|
memory +support_bytestring +support_deepseq,
|
||||||
|
any.mime-types ==0.1.0.9,
|
||||||
|
any.mmorph ==1.2.0,
|
||||||
|
any.monad-control ==1.0.3.1,
|
||||||
|
any.mtl ==2.2.2,
|
||||||
|
any.mwc-random ==0.15.0.2,
|
||||||
|
any.network ==3.1.2.7,
|
||||||
|
network -devel,
|
||||||
|
any.network-byte-order ==0.1.6,
|
||||||
|
any.network-uri ==2.6.4.1,
|
||||||
|
any.old-locale ==1.0.0.7,
|
||||||
|
any.old-time ==1.1.0.3,
|
||||||
|
any.optparse-applicative ==0.16.1.0,
|
||||||
|
optparse-applicative +process,
|
||||||
|
any.parsec ==3.1.15.0,
|
||||||
|
any.pem ==0.2.4,
|
||||||
|
any.pretty ==1.1.3.6,
|
||||||
|
any.primitive ==0.7.3.0,
|
||||||
|
any.process ==1.6.14.0,
|
||||||
|
any.prometheus-client ==1.1.0,
|
||||||
|
any.prometheus-metrics-ghc ==1.0.1.2,
|
||||||
|
any.prometheus-proc ==0.1.4.0,
|
||||||
|
any.psqueues ==0.2.7.3,
|
||||||
|
any.random ==1.2.1,
|
||||||
|
any.regex-applicative ==0.3.4,
|
||||||
|
any.resourcet ==1.2.4.3,
|
||||||
|
any.rts ==1.0.1,
|
||||||
|
any.scientific ==0.3.7.0,
|
||||||
|
scientific -bytestring-builder -integer-simple,
|
||||||
|
any.semialign ==1.2.0.1,
|
||||||
|
semialign +semigroupoids,
|
||||||
|
any.semigroupoids ==5.3.7,
|
||||||
|
semigroupoids +comonad +containers +contravariant +distributive +tagged +unordered-containers,
|
||||||
|
any.semigroups ==0.19.2,
|
||||||
|
semigroups +binary +bytestring -bytestring-builder +containers +deepseq +hashable +tagged +template-haskell +text +transformers +unordered-containers,
|
||||||
|
any.servant ==0.19,
|
||||||
|
any.servant-server ==0.19.1,
|
||||||
|
any.simple-sendfile ==0.2.30,
|
||||||
|
simple-sendfile +allow-bsd,
|
||||||
|
any.singleton-bool ==0.1.6,
|
||||||
|
any.some ==1.0.3,
|
||||||
|
some +newtype-unsafe,
|
||||||
|
any.sop-core ==0.5.0.2,
|
||||||
|
any.splitmix ==0.1.0.4,
|
||||||
|
splitmix -optimised-mixer,
|
||||||
|
any.sqlite-simple ==0.4.18.0,
|
||||||
|
any.stm ==2.5.0.1,
|
||||||
|
any.streaming-commons ==0.2.2.4,
|
||||||
|
streaming-commons -use-bytestring-builder,
|
||||||
|
any.strict ==0.4.0.1,
|
||||||
|
strict +assoc,
|
||||||
|
any.string-conversions ==0.4.0.1,
|
||||||
|
any.tagged ==0.8.6.1,
|
||||||
|
tagged +deepseq +transformers,
|
||||||
|
any.template-haskell ==2.16.0.0,
|
||||||
|
any.text ==2.0,
|
||||||
|
text -developer +simdutf,
|
||||||
|
any.text-short ==0.1.5,
|
||||||
|
text-short -asserts,
|
||||||
|
any.th-abstraction ==0.4.3.0,
|
||||||
|
any.th-compat ==0.1.3,
|
||||||
|
any.these ==1.1.1.1,
|
||||||
|
these +assoc,
|
||||||
|
any.time ==1.9.3,
|
||||||
|
any.time-compat ==1.9.6.1,
|
||||||
|
time-compat -old-locale,
|
||||||
|
any.time-manager ==0.0.0,
|
||||||
|
any.transformers ==0.5.6.2,
|
||||||
|
any.transformers-base ==0.4.6,
|
||||||
|
transformers-base +orphaninstances,
|
||||||
|
any.transformers-compat ==0.7.1,
|
||||||
|
transformers-compat -five +five-three -four +generic-deriving +mtl -three -two,
|
||||||
|
any.type-equality ==1,
|
||||||
|
any.unix ==2.7.2.2,
|
||||||
|
any.unix-compat ==0.5.4,
|
||||||
|
unix-compat -old-time,
|
||||||
|
any.unix-memory ==0.1.2,
|
||||||
|
any.unix-time ==0.4.7,
|
||||||
|
any.unliftio ==0.2.21.0,
|
||||||
|
any.unliftio-core ==0.2.0.1,
|
||||||
|
any.unordered-containers ==0.2.18.0,
|
||||||
|
unordered-containers -debug,
|
||||||
|
any.utf8-string ==1.0.2,
|
||||||
|
any.uuid-types ==1.0.5,
|
||||||
|
any.vault ==0.3.1.5,
|
||||||
|
vault +useghc,
|
||||||
|
any.vector ==0.12.3.1,
|
||||||
|
vector +boundschecks -internalchecks -unsafechecks -wall,
|
||||||
|
any.vector-algorithms ==0.8.0.4,
|
||||||
|
vector-algorithms +bench +boundschecks -internalchecks -llvm +properties -unsafechecks,
|
||||||
|
any.wai ==3.2.3,
|
||||||
|
any.wai-app-static ==3.1.7.4,
|
||||||
|
wai-app-static +cryptonite -print,
|
||||||
|
any.wai-extra ==3.1.8,
|
||||||
|
wai-extra -build-example,
|
||||||
|
any.wai-logger ==2.4.0,
|
||||||
|
any.wai-middleware-prometheus ==1.0.0.1,
|
||||||
|
any.warp ==3.3.20,
|
||||||
|
warp +allow-sendfilefd -network-bytestring -warp-debug +x509,
|
||||||
|
any.witherable ==0.4.2,
|
||||||
|
any.word8 ==0.1.3,
|
||||||
|
any.x509 ==1.7.6,
|
||||||
|
any.zlib ==0.6.2.3,
|
||||||
|
zlib -bundled-c-zlib -non-blocking-ffi -pkg-config
|
||||||
|
index-state: hackage.haskell.org 2022-04-09T11:10:06Z
|
35
lib/Soostone.hs
Normal file
35
lib/Soostone.hs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
module Soostone (
|
||||||
|
app
|
||||||
|
, middleWare
|
||||||
|
, module Soostone.API
|
||||||
|
, module Soostone.Types
|
||||||
|
, module Soostone.Handlers
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Soostone.API
|
||||||
|
import Soostone.Handlers
|
||||||
|
import Soostone.Types
|
||||||
|
|
||||||
|
import Control.Monad.Reader (runReaderT)
|
||||||
|
import Network.Wai ( Middleware )
|
||||||
|
import Network.Wai.Middleware.Prometheus ( def, prometheus )
|
||||||
|
import Servant
|
||||||
|
( Application, HasServer(ServerT), Handler, hoistServer, serve )
|
||||||
|
|
||||||
|
|
||||||
|
soostoneServer :: ServerT SoostoneAPI AppM
|
||||||
|
soostoneServer = API api'
|
||||||
|
where
|
||||||
|
api' = MainAPI submitKeyHandler countKeyHandler
|
||||||
|
|
||||||
|
app :: AppState -> Application
|
||||||
|
app !s = \req resp ->
|
||||||
|
serve api (hoistServer api nt soostoneServer) req resp
|
||||||
|
where
|
||||||
|
nt :: AppM a -> Handler a
|
||||||
|
nt x = flip runReaderT s $ runAppM x
|
||||||
|
|
||||||
|
|
||||||
|
middleWare :: Middleware
|
||||||
|
middleWare baseApp =
|
||||||
|
prometheus def baseApp
|
36
lib/Soostone/API.hs
Normal file
36
lib/Soostone/API.hs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{-# LANGUAGE DataKinds #-}
|
||||||
|
{-# LANGUAGE TypeOperators #-}
|
||||||
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
|
||||||
|
module Soostone.API where
|
||||||
|
|
||||||
|
import Soostone.Types ( Count, Key )
|
||||||
|
|
||||||
|
import Servant
|
||||||
|
( Proxy(..),
|
||||||
|
JSON,
|
||||||
|
type (:>),
|
||||||
|
ReqBody,
|
||||||
|
Post,
|
||||||
|
QueryParam',
|
||||||
|
Required,
|
||||||
|
Strict,
|
||||||
|
Get,
|
||||||
|
NamedRoutes )
|
||||||
|
import Servant.API.Generic ( Generic, GenericMode(type (:-)) )
|
||||||
|
|
||||||
|
|
||||||
|
type SoostoneAPI = NamedRoutes API
|
||||||
|
type APIVersion = "v1"
|
||||||
|
|
||||||
|
data API mode = API {
|
||||||
|
mainAPI :: mode :- "api" :> APIVersion :> NamedRoutes MainAPI
|
||||||
|
} deriving Generic
|
||||||
|
|
||||||
|
data MainAPI mode = MainAPI {
|
||||||
|
submitKey :: mode :- "input" :> ReqBody '[JSON] Key :> Post '[JSON] ()
|
||||||
|
, countKey :: mode :- "query" :> QueryParam' '[Required, Strict] "key" Key :> Get '[JSON] Count
|
||||||
|
} deriving Generic
|
||||||
|
|
||||||
|
api :: Proxy SoostoneAPI
|
||||||
|
api = Proxy :: Proxy SoostoneAPI
|
46
lib/Soostone/Handlers.hs
Normal file
46
lib/Soostone/Handlers.hs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE TypeApplications #-}
|
||||||
|
|
||||||
|
module Soostone.Handlers where
|
||||||
|
|
||||||
|
import Soostone.Types
|
||||||
|
( AppState(AppState, sqliteFile), AppM, Count(..), Key )
|
||||||
|
|
||||||
|
import Data.Functor ( ($>) )
|
||||||
|
import Database.SQLite.Simple
|
||||||
|
( open,
|
||||||
|
Connection,
|
||||||
|
close,
|
||||||
|
executeNamed,
|
||||||
|
queryNamed,
|
||||||
|
withImmediateTransaction,
|
||||||
|
NamedParam((:=)) )
|
||||||
|
import Control.Monad.Catch ( finally )
|
||||||
|
import Control.Monad.Reader ( ask )
|
||||||
|
import Control.Monad.IO.Class ( liftIO )
|
||||||
|
|
||||||
|
|
||||||
|
submitKeyHandler :: Key -> AppM ()
|
||||||
|
submitKeyHandler key = withSQLiteCon $ \con -> do
|
||||||
|
r <- liftIO $ withImmediateTransaction con $ do
|
||||||
|
queryNamed @Count con "SELECT count FROM keys WHERE key=:key" [":key" := key] >>= \case
|
||||||
|
[] -> executeNamed con "INSERT INTO keys (key, count) VALUES (:key, :count)" [":key" := key, ":count" := Count 1] $> Right ()
|
||||||
|
[c] -> executeNamed con "UPDATE keys SET count = :count WHERE key = :key" [":count" := c + 1, ":key" := key] $> Right ()
|
||||||
|
_ -> pure $ Left "Oops"
|
||||||
|
case r of
|
||||||
|
Right _ -> pure ()
|
||||||
|
Left e -> fail e -- internal error
|
||||||
|
|
||||||
|
|
||||||
|
countKeyHandler :: Key -> AppM Count
|
||||||
|
countKeyHandler key = withSQLiteCon $ \con -> do
|
||||||
|
liftIO (queryNamed @Count con "SELECT count FROM keys WHERE key=:key" [":key" := key]) >>= \case
|
||||||
|
[] -> return (Count 0)
|
||||||
|
[c] -> pure c
|
||||||
|
_ -> fail "Oops" -- internal error
|
||||||
|
|
||||||
|
withSQLiteCon :: (Connection -> AppM a) -> AppM a
|
||||||
|
withSQLiteCon action = do
|
||||||
|
AppState{..} <- ask
|
||||||
|
conn <- liftIO $ open sqliteFile
|
||||||
|
action conn `finally` liftIO (close conn)
|
60
lib/Soostone/Types.hs
Normal file
60
lib/Soostone/Types.hs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||||
|
|
||||||
|
module Soostone.Types where
|
||||||
|
|
||||||
|
import Control.Monad.Catch ( MonadMask, MonadCatch, MonadThrow )
|
||||||
|
import Control.Monad.Error.Class ( MonadError(throwError) )
|
||||||
|
import Control.Monad.IO.Class ( MonadIO(..) )
|
||||||
|
import Control.Monad.Reader.Class ( MonadReader )
|
||||||
|
import Control.Monad.Trans.Reader (ReaderT)
|
||||||
|
import Data.Aeson
|
||||||
|
( defaultOptions, genericToEncoding, FromJSON, ToJSON(toEncoding) )
|
||||||
|
import Data.String ( IsString(fromString) )
|
||||||
|
import Data.Text ( Text )
|
||||||
|
import Database.SQLite.Simple ( field, FromRow(..) )
|
||||||
|
import Database.SQLite.Simple.ToField ( ToField(..) )
|
||||||
|
import Servant
|
||||||
|
( Handler, ServerError(errBody), FromHttpApiData, err500 )
|
||||||
|
import Servant.API.Generic ( Generic )
|
||||||
|
|
||||||
|
|
||||||
|
newtype Key = Key Text
|
||||||
|
deriving (FromHttpApiData, Generic)
|
||||||
|
|
||||||
|
instance ToField Key where
|
||||||
|
toField (Key k) = toField k
|
||||||
|
|
||||||
|
instance ToJSON Key where
|
||||||
|
toEncoding = genericToEncoding defaultOptions
|
||||||
|
instance FromJSON Key
|
||||||
|
|
||||||
|
|
||||||
|
newtype Count = Count Integer
|
||||||
|
deriving (Generic, Num)
|
||||||
|
|
||||||
|
instance FromRow Count where
|
||||||
|
fromRow = Count <$> field
|
||||||
|
|
||||||
|
instance ToField Count where
|
||||||
|
toField (Count c) = toField c
|
||||||
|
|
||||||
|
|
||||||
|
instance ToJSON Count where
|
||||||
|
toEncoding = genericToEncoding defaultOptions
|
||||||
|
instance FromJSON Count
|
||||||
|
|
||||||
|
data AppState = AppState {
|
||||||
|
sqliteFile :: FilePath
|
||||||
|
}
|
||||||
|
|
||||||
|
newtype AppM a = AppM { runAppM :: ReaderT AppState Handler a }
|
||||||
|
deriving
|
||||||
|
( Functor, Applicative, Monad, MonadIO, Generic
|
||||||
|
, MonadThrow, MonadCatch, MonadMask
|
||||||
|
, MonadReader AppState
|
||||||
|
, MonadError ServerError
|
||||||
|
)
|
||||||
|
|
||||||
|
instance MonadFail AppM where
|
||||||
|
fail str = throwError $ err500 { errBody = fromString str }
|
69
soostone.cabal
Normal file
69
soostone.cabal
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
cabal-version: 2.4
|
||||||
|
name: soostone
|
||||||
|
version: 0.1.0.0
|
||||||
|
synopsis: A stringy backend
|
||||||
|
description: A really stringy backend
|
||||||
|
bug-reports: https://gogs.hasufell.de/hasufell/soostone/issues
|
||||||
|
license: BSD-3-Clause
|
||||||
|
author: Julian Ospald
|
||||||
|
maintainer: hasufell@posteo.de
|
||||||
|
copyright: 2022 Julian Ospald
|
||||||
|
category: Backend
|
||||||
|
extra-source-files: CHANGELOG.md
|
||||||
|
|
||||||
|
source-repository head
|
||||||
|
type: git
|
||||||
|
location: https://gogs.hasufell.de/hasufell/soostone.git
|
||||||
|
|
||||||
|
common deps
|
||||||
|
build-depends:
|
||||||
|
, aeson ^>=2.0
|
||||||
|
, base >=4.13 && <5.0
|
||||||
|
, bytestring ^>=0.11
|
||||||
|
, exceptions ^>=0.10
|
||||||
|
, mtl ^>=2.2
|
||||||
|
, optparse-applicative ^>=0.16
|
||||||
|
, prometheus-client ^>=1.1
|
||||||
|
, prometheus-metrics-ghc ^>=1.0
|
||||||
|
, prometheus-proc ^>=0.1
|
||||||
|
, servant ^>=0.19
|
||||||
|
, servant-server ^>=0.19
|
||||||
|
, sqlite-simple ^>=0.4
|
||||||
|
, text ^>=2.0
|
||||||
|
, transformers ^>=0.5
|
||||||
|
, wai ^>=3.2.3
|
||||||
|
, wai-middleware-prometheus ^>=1.0
|
||||||
|
, warp ^>=3.3.19
|
||||||
|
|
||||||
|
default-extensions:
|
||||||
|
BangPatterns
|
||||||
|
ImportQualifiedPost
|
||||||
|
LambdaCase
|
||||||
|
MultiWayIf
|
||||||
|
PackageImports
|
||||||
|
RecordWildCards
|
||||||
|
ScopedTypeVariables
|
||||||
|
StrictData
|
||||||
|
TupleSections
|
||||||
|
|
||||||
|
default-language: Haskell2010
|
||||||
|
ghc-options:
|
||||||
|
-Wall -fwarn-tabs -fwarn-incomplete-uni-patterns
|
||||||
|
-fwarn-incomplete-record-updates
|
||||||
|
|
||||||
|
executable soostone
|
||||||
|
import: deps
|
||||||
|
main-is: Main.hs
|
||||||
|
other-modules: Paths_soostone
|
||||||
|
autogen-modules: Paths_soostone
|
||||||
|
hs-source-dirs: app
|
||||||
|
build-depends: soostone
|
||||||
|
|
||||||
|
library
|
||||||
|
import: deps
|
||||||
|
hs-source-dirs: lib
|
||||||
|
exposed-modules:
|
||||||
|
Soostone
|
||||||
|
Soostone.API
|
||||||
|
Soostone.Handlers
|
||||||
|
Soostone.Types
|
Loading…
Reference in New Issue
Block a user