Intro
News and notifications are used in the game to let the players know something noteworthy has happened. It could be discovery of a new planet or construction project finally finishing.
All relevant information in the news is hyperlinked. If news mentions a planet, player can click the link and view current information of that planet.
Server interface
Server has three resources for news, although we’re concentrating only one here:
/api/message ApiMessageR GET POST
/api/message/#NewsId ApiMessageIdR DELETE
/api/icon ApiMessageIcons GET
First one is for retrieving all messages and posting a new one. Second one is for marking one read and third one is for retrieving all icons that players can attach to messages written by them.
Database
Database is defined in /config/models file. For news, there’s only one table:
News json
content Text
factionId FactionId
date Int
dismissed Bool
deriving Show Read Eq
Content field contains the actual news article data as serialized JSON. This allows storing complex data, without having to have lots of columns or multiple tables.
Domain objects
There are many kinds of messages that players might see, but we’ll concentrate on one about discovering a new planet
All different kinds of articles are of same type: NewsArticle. Each different kind of article has their own value constructor (PlanetFound in this particular case). And each of those value constructors has single parameter of a specific type that holds information particular to that certain article (PlanetFoundNews in this case). Adding a new article means adding a new value constructor and record to hold the data.
data NewsArticle =
StarFound StarFoundNews
| PlanetFound PlanetFoundNews
| UserWritten UserWrittenNews
| DesignCreated DesignCreatedNews
| ConstructionFinished ConstructionFinishedNews
data PlanetFoundNews = PlanetFoundNews
{ planetFoundNewsPlanetName :: Text
, planetFoundNewsSystemName :: Text
, planetFoundNewsSystemId :: Key StarSystem
, planetFoundNewsPlanetId :: Key Planet
, planetFoundNewsDate :: Int
}
Given a News object, we can turn it into NewsArticle. These are much nicer to deal with that densely packed News that is stored in database:
parseNews :: News -> Maybe NewsArticle
parseNews =
decode . toLazyByteString . encodeUtf8Builder . newsContent
Because parsing arbitrary JSON might fail, we get Maybe NewsArticle, instead of NewsArticle. It is possible to write the same code in longer way:
parseNews news =
let
content = newsContent news
utf8Encoded = encodeUtf8Builder content
byteString = toLazyByteString utf8Encoded
in
decode byteString
Similarly there’s two other functions for dealing with Entities (primary key, data - pair really) and list of Entities. Note that parseNewsEntities filters out all News that it didn’t manage to turn into NewsArticle. They have following signatures:
parseNewsEntity :: Entity News -> (Key News, Maybe NewsArticle)
parseNewsEntities :: [Entity News] -> [(Key News, NewsArticle)]
Writing JSON encoding and decoding is tedious, template Haskell can help us here:
$(deriveJSON defaultOptions ''PlanetFoundNews)
$(deriveJSON defaultOptions ''NewsArticle)
Turning Articles into JSON
News articles aren’t much use if they stay on the server, we need to send them to clients too. We can’t have multiple declarations of same typeclass for any type, so we declare complete ne