3 Commits

Author SHA1 Message Date
225e44d1a2 Insert the visibility information
- It's still not actually used anywhere
2018-08-15 22:37:32 +03:00
00a7e3d524 DB: Visibility #3 2018-08-15 22:26:01 +03:00
8b04f3760e Complete functions (#15) 2018-08-15 22:11:30 +03:00
7 changed files with 62 additions and 33 deletions

View File

@ -0,0 +1 @@
alter table channels add column visibility text NOT NULL default 'Private';

View File

@ -97,9 +97,9 @@ postBookMetaHandler auth PostBook{..} = flip requireLoggedIn auth $ \SafeUser{us
putBookMetaHandler :: AuthResult SafeUser -> BookID -> JsonBook -> AppM JsonBook putBookMetaHandler :: AuthResult SafeUser -> BookID -> JsonBook -> AppM JsonBook
putBookMetaHandler auth bookId b@JsonBook{..} putBookMetaHandler auth bookId JsonBook{..}
| bookId == identifier = requireBookOwner auth bookId $ \SafeUser{username=owner} -> | bookId == identifier = requireBookOwner auth bookId $ \SafeUser{username=owner} ->
maybe (throwM err403) (const (return b)) =<< runDB (updateBook UpdateBook{..}) maybe (throwM err403) (return . view (super @JsonBook)) =<< runDB (updateBook UpdateBook{..})
| otherwise = throwM err403 | otherwise = throwM err403
listBooksHandler :: AuthResult SafeUser -> AppM [JsonBook] listBooksHandler :: AuthResult SafeUser -> AppM [JsonBook]

View File

@ -25,14 +25,16 @@ import Data.Aeson
import Control.Lens import Control.Lens
import Data.Generics.Product import Data.Generics.Product
data JsonChannel = JsonChannel { channel :: Text } deriving (Show, Generic) data JsonChannel = JsonChannel { channel :: Text
, visibility :: Visibility }
deriving (Show, Generic)
instance ToJSON JsonChannel instance ToJSON JsonChannel
instance FromJSON JsonChannel instance FromJSON JsonChannel
type API = Auth '[SA.BasicAuth, SA.Cookie, SA.JWT] SafeUser :> BaseAPI type API = Auth '[SA.BasicAuth, SA.Cookie, SA.JWT] SafeUser :> BaseAPI
type BaseAPI = "channels" :> ReqBody '[JSON] JsonChannel :> Put '[JSON] JsonChannel type BaseAPI = "channels" :> ReqBody '[JSON] JsonChannel :> Post '[JSON] JsonChannel
:<|> "channels" :> Get '[JSON] [JsonChannel] :<|> "channels" :> Get '[JSON] [JsonChannel]
handler :: ServerT API AppM handler :: ServerT API AppM
@ -47,5 +49,5 @@ listChannelsHandler = requireLoggedIn $ \user ->
newChannelHandler :: AuthResult SafeUser -> JsonChannel -> AppM JsonChannel newChannelHandler :: AuthResult SafeUser -> JsonChannel -> AppM JsonChannel
newChannelHandler auth ch@JsonChannel{..} = flip requireLoggedIn auth $ \user -> do newChannelHandler auth ch@JsonChannel{..} = flip requireLoggedIn auth $ \user -> do
$logInfo $ "Creating channel for user " <> pack (show user) $logInfo $ "Creating channel for user " <> pack (show user)
runDB (insertChannel (view (field @"username") user) channel) runDB (insertChannel (view (field @"username") user) channel visibility)
return ch return ch

View File

@ -23,8 +23,11 @@ import Database
import Database.Selda import Database.Selda
import Database.Selda.Generic import Database.Selda.Generic
import Database.Tag (attachTag, clearTags) import Control.Lens (view)
import Database.Channel (attachChannel, clearChannels) import Data.Generics.Product
import Database.Tag (booksTags, attachTag, clearTags)
import Database.Channel (booksChannels, attachChannel, clearChannels)
usersBooks :: (MonadSelda m, MonadMask m, MonadIO m) => Username -> m [Book] usersBooks :: (MonadSelda m, MonadMask m, MonadIO m) => Username -> m [Book]
usersBooks username = fromRels <$> query q usersBooks username = fromRels <$> query q
@ -70,6 +73,7 @@ data UpdateBook = UpdateBook { identifier :: BookID
, owner :: Username , owner :: Username
, tags :: [Text] , tags :: [Text]
, channels :: [Text] } , channels :: [Text] }
deriving (Show, Generic)
bookExists :: (MonadSelda m, MonadMask m, MonadIO m) => BookID -> m Bool bookExists :: (MonadSelda m, MonadMask m, MonadIO m) => BookID -> m Bool
bookExists identifier = not . null <$> query q bookExists identifier = not . null <$> query q
@ -92,23 +96,32 @@ bookOwner' identifier username = do
return (userId :*: bookId) return (userId :*: bookId)
updateBook :: (MonadSelda m, MonadMask m, MonadIO m) => UpdateBook -> m (Maybe UpdateBook) updateBook :: (MonadSelda m, MonadMask m, MonadIO m) => UpdateBook -> m (Maybe UpdateBook)
updateBook book@UpdateBook{..} = do updateBook UpdateBook{..} = do
clearTags identifier >> connectTags clearTags identifier >> connectTags
clearChannels identifier >> connectChannels clearChannels identifier >> connectChannels
updateBook' updateBook'
getUpdateBook identifier owner
where where
connectTags = mapM_ (attachTag owner identifier) tags connectTags = mapM_ (attachTag owner identifier) tags
connectChannels = mapM_ (attachChannel owner identifier) channels connectChannels = mapM_ (attachChannel owner identifier) channels
updateBook' = do updateBook' = do
mUserId <- query (bookOwner' identifier owner) mUserId <- query (bookOwner' identifier owner)
forM (listToMaybe mUserId) $ \_userId -> do forM_ (listToMaybe mUserId) $ \_userId -> do
update_ (gen books) predicate (\b -> b `with` [ pContentType := literal contentType update_ (gen books) predicate (\b -> b `with` [ pContentType := literal contentType
, pTitle := literal title , pTitle := literal title
, pDescription := literal description ]) , pDescription := literal description ])
return book
_ :*: _ :*: pContentType :*: pTitle :*: pDescription :*: _ = selectors (gen books) _ :*: _ :*: pContentType :*: pTitle :*: pDescription :*: _ = selectors (gen books)
predicate (bookId :*: _) = bookId .== literal identifier predicate (bookId :*: _) = bookId .== literal identifier
getUpdateBook :: (MonadMask m, MonadIO m, MonadSelda m) => BookID -> Username -> m (Maybe UpdateBook)
getUpdateBook bookId username = do
mBook <- getBook bookId username
forM mBook $ \Book{..} -> do
channels <- map (view (field @"channel")) <$> booksChannels bookId
tags <- map (view (field @"tag")) <$> booksTags bookId
return UpdateBook{owner=username,..}
setContent :: (MonadSelda m, MonadMask m, MonadIO m) => BookID -> Username -> HashDigest -> m () setContent :: (MonadSelda m, MonadMask m, MonadIO m) => BookID -> Username -> HashDigest -> m ()
setContent identifier owner digest = do setContent identifier owner digest = do
mOwner <- query (bookOwner' identifier owner) mOwner <- query (bookOwner' identifier owner)

View File

@ -5,6 +5,7 @@ module Database.Channel
( userChannels ( userChannels
, insertChannel , insertChannel
, attachChannel , attachChannel
, Visibility(..)
, clearChannels , clearChannels
, booksChannels , booksChannels
, Channel(..) , Channel(..)
@ -22,24 +23,24 @@ userChannels username = fromRels <$> query q
where where
q = do q = do
userId :*: _ :*: username' :*: _ <- select (gen users) userId :*: _ :*: username' :*: _ <- select (gen users)
channel@(_ :*: _ :*: owner) <- select (gen channels) channel@(_ :*: _ :*: owner :*: _) <- select (gen channels)
restrict (owner .== userId) restrict (owner .== userId)
restrict (username' .== literal username) restrict (username' .== literal username)
return channel return channel
insertChannel :: (MonadMask m, MonadIO m) => Username -> Text -> SeldaT m () insertChannel :: (MonadMask m, MonadIO m) => Username -> Text -> Visibility -> SeldaT m ()
insertChannel username channel = do insertChannel username channel visibility = do
mUserId <- listToMaybe <$> getUser mUserId <- listToMaybe <$> getUser
void $ forM mUserId $ \userId -> void $ forM mUserId $ \userId ->
insertUnless (gen channels) (doesNotExist userId) [ def :*: channel :*: userId ] insertUnless (gen channels) (doesNotExist userId) [ def :*: channel :*: userId :*: visibility ]
where where
doesNotExist userId (_ :*: channel' :*: userId') = channel' .== literal channel .&& userId' .== literal userId doesNotExist userId (_ :*: channel' :*: userId' :*: _) = channel' .== literal channel .&& userId' .== literal userId
getUser = query $ do getUser = query $ do
userId :*: _ :*: user :*: _ <- select (gen users) userId :*: _ :*: user :*: _ <- select (gen users)
restrict (user .== literal username) restrict (user .== literal username)
return userId return userId
booksChannels :: (MonadMask m, MonadIO m) => BookID -> SeldaT m [Channel] booksChannels :: (MonadSelda m, MonadMask m, MonadIO m) => BookID -> m [Channel]
booksChannels bookId = fromRels <$> query q booksChannels bookId = fromRels <$> query q
where where
q = do q = do
@ -51,8 +52,8 @@ booksChannels bookId = fromRels <$> query q
attachChannel :: (MonadMask m, MonadIO m, MonadSelda m) => Username -> BookID -> Text -> m () attachChannel :: (MonadMask m, MonadIO m, MonadSelda m) => Username -> BookID -> Text -> m ()
attachChannel username bookId channel = do attachChannel username bookId channel = do
-- XXX: test what happens if channel doesn't exist mCh <- fromRels <$> query channelQ
[Channel{identifier}] <- fromRels <$> query channelQ forM_ mCh $ \Channel{identifier} ->
whenM (null <$> query (attachQ identifier)) $ whenM (null <$> query (attachQ identifier)) $
void $ insertGen bookChannels [BookChannel identifier bookId] void $ insertGen bookChannels [BookChannel identifier bookId]
where where
@ -62,7 +63,7 @@ attachChannel username bookId channel = do
return channelId' return channelId'
channelQ = do channelQ = do
userId :*: _ :*: username' :*: _ <- select (gen users) userId :*: _ :*: username' :*: _ <- select (gen users)
ch@(_ :*: channel' :*: owner) <- select (gen channels) ch@(_ :*: channel' :*: owner :*: _) <- select (gen channels)
restrict (username' .== literal username) restrict (username' .== literal username)
restrict (owner .== userId) restrict (owner .== userId)
restrict (channel' .== literal channel) restrict (channel' .== literal channel)

View File

@ -124,9 +124,22 @@ data Tag = Tag { identifier :: TagID
, owner :: UserID } , owner :: UserID }
deriving (Show, Generic) deriving (Show, Generic)
data Visibility = Public | Private | Followers
deriving (Show, Read, Generic)
instance ToJSON Visibility
instance FromJSON Visibility
instance SqlType Visibility where
mkLit = LCustom . LText . pack . show
fromSql (SqlString x) = fromMaybe (error "fromSql: Not a valid visibility token") . readMay . unpack $ x
fromSql _ = error "fromSql: Not a valid visibility token"
defaultValue = mkLit Private
data Channel = Channel { identifier :: ChannelID data Channel = Channel { identifier :: ChannelID
, channel :: Text , channel :: Text
, owner :: UserID } , owner :: UserID
, visibility :: Visibility }
deriving (Show, Generic) deriving (Show, Generic)
tags :: GenTable Tag tags :: GenTable Tag

View File

@ -16,15 +16,13 @@ import Database.Schema
import Database import Database
import Database.Selda import Database.Selda
import Database.Selda.Generic import Database.Selda.Generic
import Control.Monad.Trans.Maybe
upsertTag :: (MonadMask m, MonadIO m, MonadSelda m) => Username -> Text -> m Tag upsertTag :: (MonadMask m, MonadIO m, MonadSelda m) => Username -> Text -> m (Maybe Tag)
upsertTag username tag = do upsertTag username tag = runMaybeT $ do
-- I want this to error out if some data is invariant is wrong and roll back userId <- MaybeT (listToMaybe <$> query userQ)
-- the transaction. Also as a side note, run this in a transaction plz void $ lift $ upsert (gen tags) (predicate userId) id [toRel (Tag def tag userId)]
[userId] <- query userQ MaybeT (listToMaybe . fromRels <$> query (tagQ userId))
void $ upsert (gen tags) (predicate userId) id [toRel (Tag def tag userId)]
[t] <- fromRels <$> query (tagQ userId)
return t
where where
predicate userId (_ :*: tag' :*: owner) = tag' .== literal tag .&& owner .== literal userId predicate userId (_ :*: tag' :*: owner) = tag' .== literal tag .&& owner .== literal userId
tagQ userId = do tagQ userId = do
@ -48,7 +46,8 @@ booksTags bookId = fromRels <$> query q
attachTag :: (MonadMask m, MonadIO m, MonadSelda m) => Username -> BookID -> Text -> m () attachTag :: (MonadMask m, MonadIO m, MonadSelda m) => Username -> BookID -> Text -> m ()
attachTag username bookId tag = do attachTag username bookId tag = do
Tag{identifier} <- upsertTag username tag maybeT <- upsertTag username tag
forM_ maybeT $ \Tag{identifier} -> do
whenM (null <$> query (tagQ identifier)) $ whenM (null <$> query (tagQ identifier)) $
void $ insertGen bookTags [BookTag identifier bookId] void $ insertGen bookTags [BookTag identifier bookId]
where where