{-# LANGUAGE TemplateHaskell, FlexibleInstances, FlexibleContexts, MultiParamTypeClasses, DeriveDataTypeable, TypeFamilies, TypeSynonymInstances, PatternSignatures #-} module AppStateGraphBased where import qualified Data.Map as M import qualified Data.Set as S import Data.List import Control.Monad.Reader import Control.Monad.State (modify,put,get,gets) import Data.Generics import Happstack.State import SerializeableSessions import SerializeableSocialGraph import Misc import SerializeableTree {- DynGraphUqL is class hack datastructure for a graph with unique labels, with functions for non-unique-labeled graphs hidden from imported Data.Graph.Inductive (Eg mkGraph, insNode, etc are hidden) So don't import anything from Data.Graph.Inductive. Export whatever you need in UniqueLabelsGraph class (Ord a, DynGraph gr) => DynGraphUqL a gr where ........ -} import UniqueLabelsGraph -- Think of appdatastore as the database in a traditional web app. -- Data there gets stored permanently -- Data in appsessions is stored permanently too, but we don't care as much about its persistence, -- it's just to keep track of who is logged in at a point in time. -- appsessions field could be less complicated, just have M.Map Int SessionData -- don't really see the advantage of declaring a wrapper over map. -- Works! At least, it compiles :) data AppState = AppState { appsessions :: Sessions SessionData, appdatastore :: SocialGraph } deriving (Show,Read,Typeable,Data) instance Version AppState $(deriveSerialize ''AppState) instance Component AppState where type Dependencies AppState = End initialValue = AppState { appsessions = (Sessions M.empty), appdatastore = SocialGraph empty } askDatastore = do (s :: AppState ) <- ask return . appdatastore $ s askUsersGraph = fmap socialgraph askDatastore askUsersSet :: Query AppState (S.Set User) askUsersSet = fmap labelsetFromGraph askUsersGraph askSessions :: Query AppState (Sessions SessionData) askSessions = fmap appsessions ask -- modUsers :: ( S.Set User -> S.Set User ) -> Update AppState () modGraph :: ( Gr User Float -> Gr User Float ) -> Update AppState () modGraph f = modify (\appS -> (AppState (appsessions appS) ( SocialGraph . f . socialgraph . appdatastore $ appS))) modSessions :: (Sessions SessionData -> Sessions SessionData) -> Update AppState () modSessions f = modify (\s -> (AppState (f $ appsessions s) (appdatastore s))) isUser :: String -> Query AppState Bool isUser name = return . S.member name . (setmap username) =<< askUsersSet -- I tried to declare a functor instance for Set a but got blocked. setmap f = S.fromList . map f . S.toList addUser :: String -> String -> Update AppState () addUser name pass = modGraph $ addLab (User name pass Nothing Nothing ) --modUsers :: ( S.Set User -> S.Set User ) -> Update AppState () modUsers f = modify (\s -> (AppState (appsessions s) (f $ appdatastore s))) changePassword :: String -> String -> String -> Update AppState () changePassword username oldpass newpass = modUsers $ changepasswordPure username oldpass newpass -- yowch! modify is delete plus insert? -- this will work for the time being, but will probably eventually need to be something smarter -- also it would be nice if the type could reflect the possibility of failure, (Maybe, Either, etc) -- but can't figure out how to do that smartly -- modify is simply delete plus insert changepasswordPure :: String -> String -> String -> SocialGraph -> SocialGraph changepasswordPure u inputtedOldpass inputtedNewpass (SocialGraph usersgraph ) = let hashedoldpass = scramblepass inputtedOldpass hashednewpass = scramblepass inputtedNewpass mbU = lookupUser ( (==u). username) usersgraph resetP olduser@(User u realoldpass mbCP mbJobs ) = if realoldpass == hashedoldpass then modLab (const $ User u hashednewpass mbCP mbJobs ) olduser usersgraph else usersgraph in SocialGraph $ maybe usersgraph resetP mbU getUser :: String -> Query AppState (Maybe User) getUser u = liftM (lookupUser ((==u) . username)) askUsersGraph lookupUser f = find f . S.toList . labelsetFromGraph listUsers :: Query AppState [String] listUsers = liftM (map username . S.toList) askUsersSet newSession :: SessionData -> Update AppState SessionKey newSession u = do key <- getRandom modSessions $ Sessions . M.insert key u . unsession return key delSession :: SessionKey -> Update AppState () delSession sk = modSessions $ Sessions . M.delete sk . unsession getSession::SessionKey -> Query AppState (Maybe SessionData) getSession key = liftM (M.lookup key . unsession) askSessions numSessions :: Query AppState Int numSessions = liftM (M.size . unsession) askSessions -- define types which are upper case of methods below, eg AddUser, AuthUser... -- these types work with HApppS query/update machinery -- in ghci, try :i AddUser $(mkMethods ''AppState ['askUsersSet , 'getUser , 'addUser , 'changePassword , 'isUser , 'listUsers , 'getSession , 'newSession , 'delSession , 'numSessions] )