summaryrefslogtreecommitdiff
path: root/fig-web/src/Fig/Web/Module/User.hs
blob: 90daa6f33608371dac833d32bfb5898efa29e83f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
module Fig.Web.Module.User
  ( public
  ) where

import Fig.Prelude

import Data.Maybe (mapMaybe)
import qualified Data.Text as Text
import qualified Data.ByteString.Char8 as BS.C8
import qualified Data.Map.Strict as Map
import qualified Data.Aeson as Aeson

import Fig.Web.Utils
import Fig.Web.Types
import qualified Fig.Web.DB as DB

getText :: MonadIO m => DB -> ByteString -> m (Maybe Text)
getText db key = do
  DB.get db key >>= \case
    Nothing -> pure Nothing
    Just v -> pure . eitherToMaybe $ decodeUtf8' v

getTextList :: MonadIO m => DB -> ByteString -> m [Text]
getTextList db key = do
  xs <- DB.lrange db key 0 (-1)
  pure $ mapMaybe (eitherToMaybe . decodeUtf8') xs

getTextValuedMap :: MonadIO m => DB -> ByteString -> m (Map.Map Text Text)
getTextValuedMap db key = do
  vs <- Map.toList <$> DB.hgetall db key
  pure . Map.fromList $ flip mapMaybe vs \(k, v) -> do
    tk <- eitherToMaybe $ decodeUtf8' k
    tv <- eitherToMaybe $ decodeUtf8' v
    Just (tk, tv)

getIntegerValuedMap :: MonadIO m => DB -> ByteString -> m (Map.Map Text Integer)
getIntegerValuedMap db key = do
  vs <- Map.toList <$> DB.hgetall db key
  pure . Map.fromList $ flip mapMaybe vs \(k, v) -> do
    tk <- eitherToMaybe $ decodeUtf8' k
    (iv, _) <- BS.C8.readInteger v
    Just (tk, iv)

data UserInfo = UserInfo
  { stats :: Map.Map Text Integer
  , talents :: Map.Map Text Integer
  , properties :: Map.Map Text Text
  , badges :: [Text]
  } deriving Generic
instance Aeson.ToJSON UserInfo 

getUserInfo :: MonadIO m => DB -> ByteString -> m UserInfo
getUserInfo db uid = do
  stats <- getIntegerValuedMap db $ "user:stats:" <> uid
  talents <- getIntegerValuedMap db $ "user:talents:" <> uid
  properties <- getTextValuedMap db $ "user:properties:" <> uid
  badges <- getTextList db $ "user:badges:" <> uid
  pure UserInfo{..}

public :: PublicModule
public a = do
  -- users
  onGet "/api/user/:name" do
    name <- Text.toLower <$> pathParam "name"
    DB.get a.db ("user:" <> encodeUtf8 name) >>= \case
      Nothing -> do
        status status404
        respondText "user not found"
      Just val -> respondText $ decodeUtf8 val
  onGet "/api/user/info/:uid" do -- get everything bundled together
    uid <- pathParam "uid"
    respondJSON =<< getUserInfo a.db uid
  onGet "/api/user/stats/:uid" do
    uid <- pathParam "uid"
    respondJSON =<< getIntegerValuedMap a.db ("user:stats:" <> uid)
  onGet "/api/user/talents/:uid" do
    uid <- pathParam "uid"
    respondJSON =<< getIntegerValuedMap a.db ("user:talents:" <> uid)
  onGet "/api/user/properties/:uid" do
    uid <- pathParam "uid"
    respondJSON =<< getTextValuedMap a.db ("user:properties:" <> uid)
  onGet "/api/user/badges/:uid" do
    uid <- pathParam "uid"
    respondJSON =<< getTextList a.db ("user:badges:" <> uid)
  onGet "/api/user/avatar/:uid.png" do
    uuidpng <- pathParam "uid.png"
    case Text.stripSuffix ".png" uuidpng of
      Nothing -> do
        status status400
        respondText "malformed user avatar path"
      Just uid -> DB.get a.db ("user:avatar:" <> encodeUtf8 uid) >>= \case
        Nothing -> do
          addHeader "Content-Type" "image/png"
          respondBytes "\137PNG\r\n\SUB\n\NUL\NUL\NUL\rIHDR\NUL\NUL\NUL\STX\NUL\NUL\NUL\STX\b\ACK\NUL\NUL\NULr\182\r$\NUL\NUL\NUL\SOHsRGB\NUL\174\206\FS\233\NUL\NUL\NUL\SUBIDAT\b\153cd``\248\207T)\206\192\192T)\254\159\129\129\225?\NUL\RS\188\EOT#\137b%\ACK\NUL\NUL\NUL\NULIEND\174B`\130"
        Just img -> do
          addHeader "Content-Type" "image/png"
          respondBytes img
  -- badges
  onGet "/api/badge/:uuid" do
    uuid <- pathParam "uuid"
    respondJSON =<< getTextList a.db ("badge:" <> uuid)