diff options
Diffstat (limited to 'Biz/Ibb')
-rw-r--r-- | Biz/Ibb/Client.hs | 38 | ||||
-rw-r--r-- | Biz/Ibb/Core.hs | 143 | ||||
-rw-r--r-- | Biz/Ibb/Influencers.hs | 407 | ||||
-rw-r--r-- | Biz/Ibb/Keep.hs | 123 | ||||
-rw-r--r-- | Biz/Ibb/Look.hs | 40 | ||||
-rw-r--r-- | Biz/Ibb/Move.hs | 48 | ||||
-rw-r--r-- | Biz/Ibb/Server.hs | 152 | ||||
-rw-r--r-- | Biz/Ibb/service.nix | 42 |
8 files changed, 993 insertions, 0 deletions
diff --git a/Biz/Ibb/Client.hs b/Biz/Ibb/Client.hs new file mode 100644 index 0000000..e40ff36 --- /dev/null +++ b/Biz/Ibb/Client.hs @@ -0,0 +1,38 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE NoImplicitPrelude #-} + +-- | Front-end +-- +-- : exe ibb.js +-- +-- : dep clay +-- : dep miso +-- : dep protolude +-- : dep servant +-- : dep text +-- : dep aeson +-- : dep containers +-- : dep ghcjs-base +module Biz.Ibb.Client where + +import Alpha +import Biz.Ibb.Core ( Action(..) + , see + , init + ) +import Biz.Ibb.Move ( move ) +import Miso ( App(..) + , defaultEvents + , miso + ) + +main :: IO () +main = miso $ \u -> App { model = init u, .. } + where + initialAction = FetchPeople + update = move + view = see + events = defaultEvents + subs = [] + mountPoint = Nothing diff --git a/Biz/Ibb/Core.hs b/Biz/Ibb/Core.hs new file mode 100644 index 0000000..fb82ff0 --- /dev/null +++ b/Biz/Ibb/Core.hs @@ -0,0 +1,143 @@ +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE OverloadedStrings #-} + +-- | Main app logic +module Biz.Ibb.Core where + +import Alpha +import Network.RemoteData +import Data.Aeson hiding ( Success ) +import Data.Data ( Data + , Typeable + ) +import Data.Text ( Text ) +import GHC.Generics ( Generic ) +import Miso +import Miso.String +import Servant.API +import Servant.Links + +-- * entity data types + +data Person = Person + { _name :: Text + -- ^ Their full name. + , _pic :: Text + -- ^ A link to their picture. + , _twitter :: Text + -- ^ Their twitter handle, without the `@` prefix. + , _website :: Text + -- ^ Their main website, fully formed: `https://example.com` + , _books :: [Book] + -- ^ A short list of the books they recommend. + , _blurb :: Text + -- ^ A short "about" section, like you would see on the jacket flap of a book. + } deriving (Generic, Show, Eq, Typeable, Data, Ord) + +instance FromJSON Person +instance ToJSON Person + +data Book = Book + { _title :: Text + , _author :: Text + , _amznref :: Text + -- ^ Amazon REF number, for creating affiliate links. + } deriving (Generic, Show, Eq, Typeable, Data, Ord) + +instance FromJSON Book +instance ToJSON Book + +-- * app data types + +type AppRoutes = Home + +type Home = View Action + +data Model = Model + { uri :: URI + , people :: WebData [Person] + } deriving (Show, Eq) + +type WebData a = RemoteData MisoString a + +init :: URI -> Model +init u = Model u Loading + +data Action + = Nop + | ChangeRoute URI + | HandleRoute URI + | FetchPeople + | SetPeople (WebData [Person]) + deriving (Show, Eq) + +home :: Model -> View Action +home m = see m + +handlers :: Model -> View Action +handlers = home + +notfound :: View Action +notfound = div_ [] [text "404"] + +goHome :: URI +goHome = linkURI $ safeLink (Proxy :: Proxy AppRoutes) (Proxy :: Proxy Home) + +see :: Model -> View Action +see m = div_ + [class_ "container mt-5"] + [ div_ + [class_ "jumbotron"] + [ h1_ [class_ "display-4"] [text "Influenced by books"] + , p_ [class_ "lead"] + [text "Influential people and the books that made them."] + , p_ + [class_ "lead"] + [ a_ + [href_ "http://eepurl.com/ghBFjv"] + [ text + "Get new book recommendations from the world's influencers in your email." + ] + ] + ] + , div_ [class_ "card-columns"] $ case people m of + NotAsked -> [text "Initializing..."] + Loading -> [text "Loading..."] + Failure err -> [text err] + Success ps -> seePerson </ ps + ] + +seePerson :: Person -> View Action +seePerson person = div_ + [class_ "card"] + [ div_ [class_ "card-img"] + [img_ [class_ "card-img img-fluid", src_ $ ms $ _pic person]] + , div_ + [class_ "card-body"] + [ h4_ [class_ "card-title"] [text $ ms $ _name person] + , h6_ + [] + [ a_ + [ class_ "fab fa-twitter" + , href_ $ "https://twitter.com/" <> (ms $ _twitter person) + ] + [] + , a_ [class_ "fas fa-globe", href_ $ ms $ _website person] [] + ] + , p_ [class_ "card-text"] + [text $ ms $ _blurb person, ul_ [] $ seeBook </ _books person] + ] + ] + +seeBook :: Book -> View Action +seeBook book = li_ + [] + [ a_ + [ class_ "text-dark" + , href_ $ "https://www.amazon.com/dp/" <> (ms $ _amznref book) + ] + [text $ ms $ _title book] + ] diff --git a/Biz/Ibb/Influencers.hs b/Biz/Ibb/Influencers.hs new file mode 100644 index 0000000..c31e962 --- /dev/null +++ b/Biz/Ibb/Influencers.hs @@ -0,0 +1,407 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DeriveDataTypeable #-} + +module Biz.Ibb.Influencers where + +import Biz.Ibb.Core + +allPeople :: [Person] +allPeople = + [ Person { _name = "Joe Rogan" + , _pic = "https://pbs.twimg.com/profile_images/552307347851210752/vrXDcTFC_400x400.jpeg" + , _twitter = "joerogan" + , _blurb = "Stand up comic/mixed martial arts fanatic/psychedelic adventurer Host of The Joe Rogan Experience" + , _website = "http://joerogan.com" + , _books = [ Book {_title = "Food of the Gods" + , _author = "Terence McKenna" + , _amznref = "0553371304" + } + , Book { _title = "The War of Art" + , _author ="Steven Pressfield" + , _amznref ="B007A4SDCG" + } + ] + } + , Person { _name = "Beyoncé" + , _pic = "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTxT84sF19lxdnSiblIXAp-Y4wAigpQn8sZ2GtAerIR_ApiiEJfFQ" + , _twitter = "Beyonce" + , _blurb = "American singer, songwriter, actress, record producer and dancer" + , _website = "http://beyonce.com" + , _books = [ Book { _title = "What Will It Take To Make A Woman President?" + , _author = "Marianne Schnall" + , _amznref = "B00E257Y7G"} + ] + } + , Person { _name = "Barrack Obama" + , _pic = "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQeLzftR36p0hYI-EKNa5fm7CYDuN-vyz23_R48ocqa8X1nPr6C" + , _twitter = "BarackObama" + , _blurb = "Dad, husband, President, citizen. 44th POTUS" + , _website = "http://barackobama.com" + , _books = [ Book { _title = "An American Marriage" + , _author = "Tayari Jones" + , _amznref = "B01NCUXEFR"} + , Book { _title = "Americanah" + , _author = "Chimamanda Ngozi Adichie" + , _amznref = "B00A9ET4MC"} + ] + } + , Person { _name = "Warren Buffet" + , _pic = "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQQbmnUykS6zqgzaf44tsq1RAsnHe6H7fapEoSqUwAoJGSFKbAPSw" + , _twitter = "WarrenBuffett" + , _blurb = "Chairman and CEO of Berkshire Hathaway" + , _website = "http://berkshirehathaway.com" + , _books = [ Book { _title = "The Intelligent Investor" + , _author = "Benjamin Graham" + , _amznref = "B000FC12C8"} + , Book { _title = "Security Analysis" + , _author = "Benjamin Graham" + , _amznref = "B0037JO5J8"} + ] + } + , Person { _name = "Bill Gates" + , _pic = "https://pbs.twimg.com/profile_images/988775660163252226/XpgonN0X_400x400.jpg" + , _twitter = "BillGates" + , _blurb = "Sharing things I'm learning through my foundation work and other interests. Founder of Microsoft and Bill & Melinda Gates Foundation" + , _website = "https://www.gatesnotes.com" + , _books = [ Book { _title = "Leonardo da Vinci" + , _author = "Walter Isaacson" + , _amznref = "1501139169" + } + , Book { _title = "Educated" + , _author = "Tara Wetsover" + , _amznref = "B072BLVM83" + } + ] + } + , Person { _name = "Stephen King" + , _pic = "https://pbs.twimg.com/profile_images/378800000836981162/b683f7509ec792c3e481ead332940cdc_400x400.jpeg" + , _twitter = "StephenKing" + , _blurb = "World renowned Author" + , _website = "https://stephenking.com/" + , _books = [ Book { _title = "Red Moon" + , _author = "Benjamin Percy" + , _amznref = "B008TU2592" + } + , Book { _title = "The Marauders" + , _author = "Tom Cooper" + , _amznref = "B00MKZBVTM" + } + ] + } + , Person { _name = "Tobi Lütke" + , _pic = "https://pbs.twimg.com/profile_images/551403375141457920/28EOlhnM_400x400.jpeg" + , _twitter = "tobi" + , _blurb = "Shopify CEO by day, Dad in the evening, hacker at night. - Rails Core alumni; Author of ActiveMerchant, Liquid. Comprehensivist" + , _website = "https://www.shopify.com" + , _books = [ Book { _title = "Influence" + , _author ="Robert B. Cialdini" + , _amznref = "006124189X" + } + , Book { _title = "High Output Management" + , _author ="Andrew S. Grove" + , _amznref = "B015VACHOK" + } + ] + } + , Person { _name = "Susan Cain" + , _pic = "https://pbs.twimg.com/profile_images/1474290079/SusanCain5smaller-1_400x400.jpg" + , _twitter = "susancain" + , _blurb = "Bestselling author, award-winning speaker, http://Quietrev.com curator. Lover of bittersweet music & bittersweet chocolate, in equal measure." + , _website = "https://www.quietrev.com" + , _books = [ Book { _title = "Bird by Bird" + , _author ="Anne Lamott" + , _amznref = "0385480016" + } + , Book { _title = "Waking Up" + , _author ="Sam Harris" + , _amznref = "1451636024" + } + ] + } + , Person { _name = "Oprah Winfrey" + , _pic = "https://pbs.twimg.com/profile_images/1013835283698049025/q5ZN4yv3_400x400.jpg" + , _twitter = "Oprah" + , _blurb = "Oprah Winfrey is an American media executive, actress, talk show host, television producer and philanthropis" + , _website = "http://www.oprah.com/index.html" + , _books = [ Book { _title = "A New Earth" + , _author ="Eckhart Tolle" + , _amznref = "B000PC0S5K" + } + , Book { _title = "The Poisonwood Bible" + , _author ="Barbara Kingsolver" + , _amznref = "B000QTE9WU" + } + ] + } + , Person { _name = "Patrick Collison" + , _pic = "https://pbs.twimg.com/profile_images/825622525342199809/_iAaSUQf_400x400.jpg" + , _twitter = "patrickc" + , _blurb = "Fallibilist, optimist. Stripe CEO" + , _website = "https://patrickcollison.com" + , _books = [ Book { _title = "How Judges Think" + , _author ="Richard A. Posner" + , _amznref = "0674048067" + } + , Book { _title = "Programmers at Work" + , _author ="Susan Lammers" + , _amznref = "1556152116" + } + ] + } + , Person { _name = "Luis Von Ahn" + , _pic = "https://pbs.twimg.com/profile_images/1020343581087678464/NIXD5MdC_400x400.jpg" + , _twitter = "LuisvonAhn" + , _blurb = "CEO & co-founder of duolingo. Invented reCAPTCHA. Computer science professor at Carnegie Mellon. Proud Guatemalan" + , _website = "https://www.duolingo.com/" + , _books = [ Book { _title = "Zero to One" + , _author ="Peter Thiel" + , _amznref = "B00J6YBOFQ" + } + , Book { _title = "The Hard Thing About Hard Things" + , _author ="Ben Horowitz" + , _amznref = "B00DQ845EA" + } + ] + } + , Person { _name = "Bryan Johnson" + , _pic = "https://pbs.twimg.com/profile_images/1055165076372475904/vNp60sSl_400x400.jpg" + , _twitter = "bryan_johnson" + , _blurb = "Founder of Kernel, OS Fund and Braintree. Trying to go where there is no destination" + , _website = "https://bryanjohnson.co" + , _books = [ Book { _title = "A Good Man" + , _author ="Mark Shriver" + , _amznref = "B007CLBH0M" + } + , Book { _title = "Shackleton" + , _author ="Nick Bertozzi" + , _amznref = "1596434511" + } + ] + } + , Person { _name = "Peter Thiel" + , _pic = "https://pbs.twimg.com/profile_images/1121220551/Peter_Thiel_400x400.jpg" + , _twitter = "peterthiel" + , _blurb = "Technology entrepreneur, investor, philanthropist." + , _website = "http://zerotoonebook.com" + , _books = [ Book { _title = "Deceit, Desire, and the Novel" + , _author ="René Girard" + , _amznref = "0801818303" + } + , Book { _title = "Violence and the Sacred" + , _author ="René Girard" + , _amznref = "0801822181" + } + ] + } + , Person { _name = "Tim Ferris" + , _pic = "https://pbs.twimg.com/profile_images/49918572/half-face-ice_400x400.jpg" + , _twitter = "tferriss" + , _blurb = "Author of 5 #1 NYT/WSJ bestsellers, investor (FB, Uber, Twitter, 50+ more: http://angel.co/tim ), host of The Tim Ferriss Show podcast (300M+ downloads)" + , _website = "http://tim.blog" + , _books = [ Book { _title = "10% Happier" + , _author ="Dan Harris" + , _amznref = "0062265431" + } + , Book { _title = "A Guide to the Good Life" + , _author ="William Irvine" + , _amznref = "B0040JHNQG" + } + ] + } + , Person { _name = "Allen Walton" + , _pic = "https://pbs.twimg.com/profile_images/1038905908678545409/yUbF9Ruc_400x400.jpg" + , _twitter = "allenthird" + , _blurb = "Created http://SpyGuy.com and blogs about stuff at http://AllenWalton.com . All things ecommerce, personal dev, and Simpsons." + , _website = "https://www.allenwalton.com" + , _books = [ Book { _title = "4 Hour Work Week" + , _author ="Tim Ferris" + , _amznref = "B002WE46UW" + } + , Book { _title = "Choose Yourself" + , _author ="James Altucher" + , _amznref = "B00CO8D3G4" + } + ] + } + , Person { _name = "Peter Mallouk" + , _pic = "https://pbs.twimg.com/profile_images/713172266968715264/KsyDYghf_400x400.jpg" + , _twitter = "PeterMallouk" + , _blurb = "President of Creative Planning. Author “5 Mistakes Every Investor Makes & How to Avoid Them”. Radically moderate." + , _website = "https://creativeplanning.com" + , _books = [ Book { _title = "Awareness" + , _author ="Anthony de Mello SJ" + , _amznref = "B005GFBP6W" + } + , Book { _title = "The Prophet" + , _author ="Kahlil Gibran" + , _amznref = "B07NDJ3LMW" + } + ] + } + , Person { _name = "Adam Robinson" + , _pic = "https://pbs.twimg.com/profile_images/822708907051077632/y5KyboMV_400x400.jpg" + , _twitter = "IAmAdamRobinson" + , _blurb = "Entrepreneur. Systems builder. Wizard. Shaman of global financial markets. Manifester. Didact. Do-gooder. Alchemist. Aphorist. Seeker. Embracer of possibility." + , _website = "http://robinsonglobalstrategies.com" + , _books = [ Book { _title = "Wishcraft" + , _author ="Barbara Sher" + , _amznref = "0345465180" + } + , Book { _title = "You Can Be a Stock Market Genius" + , _author ="Joel Greenblatt" + , _amznref = "0684832135" + } + ] + } + , Person { _name = "Andrew Weil" + , _pic = "https://pbs.twimg.com/profile_images/987461787422359553/mpoZAmPH_400x400.jpg" + , _twitter = "DrWeil" + , _blurb = "A world-renowned leader and pioneer in the field of integrative medicine, a healing oriented approach to health care which encompasses body, mind, and spirit." + , _website = "https://www.drweil.com" + , _books = [ Book { _title = "The Way Of Life According To Lao Tzu" + , _author = "Witter Byner" + , _amznref = "0399512985" + } + , Book { _title = "The Psychology of Romantic Love" + , _author ="Nathaniel Branden" + , _amznref = "B0012RMVJI" + } + ] + } + , Person { _name = "Hubert Joly" + , _pic = "https://scontent-ort2-2.xx.fbcdn.net/v/t1.0-1/c1.0.193.193a/38444401_2156120597936470_9028564067043770368_n.jpg?_nc_cat=104&_nc_ht=scontent-ort2-2.xx&oh=162142edb167f389a5b585a329e4993a&oe=5CE1D578" + , _twitter = "BBYCEO" + , _blurb = "CEO of Best Buy" + , _website = "https://www.bestbuy.com" + , _books = [ Book { _title = "Who Says Elephants Can't Dance" + , _author = "Louis. V. Gerstner" + , _amznref = "0060523808" + } + , Book { _title = "Onward" + , _author ="Howard Schultz" + , _amznref = "1609613821" + } + ] + } + , Person { _name = "Esther Perel" + , _pic = "https://pbs.twimg.com/profile_images/1091062675151319040/MzxCcgdU_400x400.jpg" + , _twitter = "EstherPerel" + , _blurb = "Exploring modern relationships. Author of MatingInCaptivity and TheStateOfAffairsBook. Podcast: WhereShouldWeBegin. Psychotherapist in NYC." + , _website = "https://www.estherperel.com" + , _books = [ Book { _title = "Crime And Punishment" + , _author = "Fyodor Dostoyevsky" + , _amznref = "B07NL94DFD" + } + , Book { _title = "If This Is a Man and The Truce" + , _author ="Primo Levi" + , _amznref = "0349100136" + } + ] + } + , Person { _name ="Neil deGrasse Tyson" + , _pic = "https://pbs.twimg.com/profile_images/74188698/NeilTysonOriginsA-Crop_400x400.jpg" + , _twitter = "neiltyson" + , _blurb = "Astrophysicistthe. Fifth head since 1935 of the world-renowned Hayden Planetarium in New York City and the first occupant of its Frederick P. Rose Directorship. Research associate of the Department of Astrophysics at the American Museum of Natural History." + , _website = "https://www.haydenplanetarium.org/tyson/" + , _books = [ Book { _title = "The Prince" + , _author = "Machiavelli" + , _amznref = "B07ND3CM16" + } + , Book { _title = "The Art of War" + , _author ="Sun Tzu" + , _amznref = "1545211957" + } + ] + } + , Person { _name = "Mark Cuban" + , _pic = "https://pbs.twimg.com/profile_images/1422637130/mccigartrophy_400x400.jpg" + , _twitter = "mcuban" + , _blurb = "Owner of Dallas Mavericks, Shark on ABC’s Shark Tank, chairman and CEO of AXS tv, and investor in an ever-growing portfolio of businesses" + , _website = "http://markcubancompanies.com/" + , _books = [ Book { _title = "The Fountainhead" + , _author = "Ayn Rend" + , _amznref = "0452273331" + } + , Book { _title = "The Gospel of Wealth " + , _author ="Andrew Carnegie" + , _amznref = "1409942171" + } + ] + } + , Person { _name = "Robert Herjavec" + , _pic = "https://pbs.twimg.com/profile_images/608643660876423170/DgxUW3eZ_400x400.jpg" + , _twitter = "robertherjavec" + , _blurb = "Dad, Husband, Founder & CEO of global cybersecurity firm HerjavecGroup, Shark on ABC’s Shark Tank, Former Dragon, Author" + , _website = "https://www.robertherjavec.com/" + , _books = [ Book { _title = "Why I Run" + , _author = "Mark Sutcliffe" + , _amznref = "B007OC9P3A" + } + , Book { _title = "Swim with the Sharks Without Being Eaten Alive" + , _author ="Harvey B. Mackay" + , _amznref = "006074281X" + } + ] + } + , Person { _name = "Caterina Fake" + , _pic = "https://pbs.twimg.com/profile_images/378800000509318185/d968d62d1bc39f2c82d3fa44db478525_400x400.jpeg" + , _twitter = "Caterina" + , _blurb = "Host, Should this Exist? Investor, Yes VC. Cofounder: Flickr, Hunch, Sesat School. Etsy. Sundance. Homeschooling, film, literature. Dogs." + , _website = "https://caterina.net" + , _books = [ Book { _title = "Growth of the Soil" + , _author = "Knut Hamsun" + , _amznref = "0343181967" + } + , Book { _title = "The Thousand Autumns of Jacob de Zoet" + , _author ="David Mitchell" + , _amznref = "0812976363" + } + ] + } + , Person { _name = "Daymond John" + , _pic = "https://pbs.twimg.com/profile_images/1048022980863954944/eZvGANn0_400x400.jpg" + , _twitter = "TheSharkDaymond" + , _blurb = "CEO of FUBU, Shark on ABC’s Shark Tank, Author." + , _website = "https://daymondjohn.com/" + , _books = [ Book { _title = "Think and Grow Rich" + , _author = "Napoleon Hill" + , _amznref = "1585424331" + } + , Book { _title = "How to Win Friends & Influence People" + , _author ="Dale Carnegie" + , _amznref = "0671027034" + } + ] + } + , Person { _name = "Kevin O'Leary" + , _pic = "https://pbs.twimg.com/profile_images/1067383195597889536/cP6tNEt0_400x400.jpg" + , _twitter = "kevinolearytv" + , _blurb = "Chairman O'Shares ETFs, 4 time Emmy Award winning Shark Tank's Mr. Wonderful, bestselling author, CNBC contributor, wine maker, guitar dude and photographer." + , _website = "http://askmrwonderful.eone.libsynpro.com/" + , _books = [ Book { _title = "Competitive Advantage" + , _author = "Michael Porter" + , _amznref = "0684841460" + } + , Book { _title = "Secrets of Closing the Sale" + , _author ="Zig Ziglar" + , _amznref = "0425081028" + } + ] + } + , Person { _name = "Alex Rodriguez" + , _pic = "https://pbs.twimg.com/profile_images/796405335388848128/LbvsjCA3_400x400.jpg" + , _twitter = "AROD" + , _blurb = "3-time MVP • 14-time All Star • World Series Champ • CEO of @_ARodCorp• @FoxSports Commentator/Analyst • Special Advisor to the Yankees, @ABCSharkTank and ESPN" + , _website = "http://www.arodcorp.com/" + , _books = [ Book { _title = "Blitzscaling" + , _author = "Reid Hoffman" + , _amznref = "1524761419" + } + , Book { _title = "Measure What Matters" + , _author ="John Doerr" + , _amznref = "0525536221" + } + ] + } + ] diff --git a/Biz/Ibb/Keep.hs b/Biz/Ibb/Keep.hs new file mode 100644 index 0000000..ad6dc5c --- /dev/null +++ b/Biz/Ibb/Keep.hs @@ -0,0 +1,123 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE TypeFamilies #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +-- | Keep is a database built on Data.Acid. +-- +-- If this proves useful, maybe we could make it a more general thing. Like +-- `Biz.Keep`. I could wrap all the safecopy stuff in my own template haskell +-- like `$(keep ''MyType)`. +-- +module Biz.Ibb.Keep where + +import Biz.Ibb.Core (Person(..), Book(..)) +import qualified Biz.Ibb.Influencers as Influencers +import Control.Monad.Reader (ask) +import Control.Monad.State (get, put) +import Data.Acid (Update, makeAcidic) +import qualified Data.Acid as Acid +import Data.Data (Data, Typeable) +import Data.IxSet (Indexable(..), IxSet, ixFun, ixSet) +import qualified Data.IxSet as IxSet +import Data.SafeCopy +import Data.Text (Text) +import qualified Data.Text as Text + +-- * Keep + +-- | Main database. Need to think of a better name for this. +data IbbKeep = IbbKeep + { _people :: IxSet Person + } + deriving (Data, Typeable) + +$(deriveSafeCopy 0 'base ''IbbKeep) + +-- * Index @Person@ + +$(deriveSafeCopy 0 'base ''Person) + +newtype PersonName = + PersonName Text deriving (Eq, Ord, Data, Typeable) + +newtype PersonBlurb = + PersonBlurb Text deriving (Eq, Ord, Data, Typeable) + +instance Indexable Person where + empty = ixSet + [ ixFun $ \p -> [ PersonName $ _name p ] + , ixFun $ \p -> [ _pic p ] + , ixFun $ \p -> [ _twitter p ] + , ixFun $ \p -> [ _website p ] + , ixFun $ \p -> [ _books p ] + , ixFun $ \p -> [ PersonBlurb $ _blurb p ] + ] + +-- | updates the `IbbKeep` with a new `Person` +newPerson :: Text -> Text -> Update IbbKeep Person +newPerson name blurb = do + k <- get + put $ k { _people = IxSet.insert p (_people k) + } + return p + where + p = Person + { _name = name + , _pic = Text.empty + , _twitter = Text.empty + , _website = Text.empty + , _books = [] + , _blurb = blurb + } + +getPeople :: Int -> Acid.Query IbbKeep [Person] +getPeople n = do + keep <- ask + return $ take n $ IxSet.toList $ _people keep + +-- * Index @Book@ + +$(deriveSafeCopy 0 'base ''Book) + +newtype BookTitle = + BookTitle Text deriving (Eq, Ord, Data, Typeable) + +newtype BookAuthor = + BookAuthor Text deriving (Eq, Ord, Data, Typeable) + +instance Indexable Book where + empty = ixSet + [ ixFun $ \b -> [ BookTitle $ _title b ] + , ixFun $ \b -> [ BookAuthor $ _author b ] + , ixFun $ \b -> [ _amznref b ] + ] + +-- | updates the `IbbKeep` with a new `Book` +--newBook :: Text -> Text -> Text -> Update IbbKeep Book +--newBook title author amznref = do +-- ibbKeep <- get +-- put $ ibbKeep { _books = IxSet.insert b (_books ibbKeep) +-- , _people = _people ibbKeep +-- } +-- return b +-- where +-- b = Book { _title = title +-- , _author = author +-- , _amznref = amznref +-- } + +-- * Opening the keep + +-- defines @NewPerson@ for us. +$(makeAcidic ''IbbKeep ['newPerson, 'getPeople]) + +initialIbbKeep :: IbbKeep +initialIbbKeep = IbbKeep + { _people = IxSet.fromList Influencers.allPeople + } + +openLocal :: String -> IO (Acid.AcidState IbbKeep) +openLocal dir = + Acid.openLocalStateFrom dir initialIbbKeep diff --git a/Biz/Ibb/Look.hs b/Biz/Ibb/Look.hs new file mode 100644 index 0000000..5f7ca6b --- /dev/null +++ b/Biz/Ibb/Look.hs @@ -0,0 +1,40 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE NoImplicitPrelude #-} + +-- | The look and feel of Ibb +module Biz.Ibb.Look where + +import Alpha hiding ( Selector ) +import Clay +import qualified Clay.Flexbox as Flexbox +import qualified Clay.Media as Media +import qualified Clay.Render as Clay +import qualified Clay.Stylesheet as Stylesheet + +main :: Css +main = do + "html" <> "body" ? do + width (pct 100) + display flex + flexDirection column + alignItems center + alignContent center + justifyContent center + ".container" ? do + maxWidth (px 900) + display flex + justifyContent center + flexDirection column + fontFamily ["GillSans", "Calibri", "Trebuchet"] [sansSerif] + headings ? do + fontFamily + [ "Palatino" + , "Palatino Linotype" + , "Hoefler Text" + , "Times New Roman" + , "Times" + ] + [serif] + +headings :: Selector +headings = h1 <> h2 <> h3 <> h4 <> h5 <> h6 diff --git a/Biz/Ibb/Move.hs b/Biz/Ibb/Move.hs new file mode 100644 index 0000000..1e635ac --- /dev/null +++ b/Biz/Ibb/Move.hs @@ -0,0 +1,48 @@ +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE OverloadedStrings #-} + +-- | App update logic +module Biz.Ibb.Move + ( move + -- * Server interactions + , fetchPeople + ) +where + +import Alpha +import Biz.Ibb.Core as Core +import Network.RemoteData +import Data.Aeson +import JavaScript.Web.XMLHttpRequest ( Request(..) + , Method(GET) + , RequestData(NoData) + , contents + , xhrByteString + ) +import Miso +import Miso.String + +move :: Action -> Model -> Effect Action Model +move Nop m = noEff m +move (HandleRoute u) m = m { uri = u } <# pure Nop +move (ChangeRoute u) m = m <# do + pushURI u >> pure Nop +move FetchPeople m = m <# (SetPeople </ fetchPeople) +move (SetPeople ps) m = noEff m { people = ps } + +fetchPeople :: IO (WebData [Core.Person]) +fetchPeople = do + mjson <- contents </ xhrByteString req + case mjson of + Nothing -> pure $ Failure "could not read from server" + Just a -> + pure $ fromEither $ either (Left . ms) pure $ eitherDecodeStrict a + where + req = Request { reqMethod = GET + -- FIXME: can replace this hardcoding with a function? + , reqURI = "/api/people" + , reqLogin = Nothing + , reqHeaders = [] + , reqWithCredentials = False + , reqData = NoData + } diff --git a/Biz/Ibb/Server.hs b/Biz/Ibb/Server.hs new file mode 100644 index 0000000..9f1ac5f --- /dev/null +++ b/Biz/Ibb/Server.hs @@ -0,0 +1,152 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE NoImplicitPrelude #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeOperators #-} + +-- | Server +-- +-- : exe ibb +-- +-- : dep clay +-- : dep miso +-- : dep protolude +-- : dep servant +-- : dep text +-- : dep MonadRandom +-- : dep acid-state +-- : dep bytestring +-- : dep ixset +-- : dep random +-- : dep safecopy +-- : dep scotty +-- : dep servant-server +-- : dep text +module Biz.Ibb.Server where + +import Alpha +import qualified Clay +import Biz.Ibb.Core +import qualified Biz.Ibb.Keep as Keep +import qualified Biz.Ibb.Look as Look +import Network.RemoteData +import Data.Acid ( AcidState ) +import qualified Data.Acid.Abstract as Acid +import Data.Maybe ( fromMaybe ) +import qualified Data.Text.Lazy as Lazy +import qualified Data.Text.Lazy.Encoding as Lazy +import qualified Lucid as L +import Lucid.Base +import Miso +import Network.HTTP.Media ( (//) + , (/:) + ) +import Network.HTTP.Types +import Network.Wai +import Network.Wai.Application.Static +import Network.Wai.Handler.Warp +import Network.Wai.Middleware.Gzip +import Network.Wai.Middleware.RequestLogger +import Servant +import System.Environment ( lookupEnv ) + +main :: IO () +main = do + say "rise: ibb" + staticDir <- fromMaybe "static" <$> lookupEnv "STATIC_DIR" :: IO [Char] + port <- read <$> fromMaybe "3000" <$> lookupEnv "PORT" :: IO Int + keep <- Keep.openLocal "_keep/" + say "port: 3000" + run port $ logStdout $ compress $ app staticDir $ keep + where compress = gzip def { gzipFiles = GzipCompress } + +newtype HtmlPage a = HtmlPage a + deriving (Show, Eq) + +instance L.ToHtml a => L.ToHtml (HtmlPage a) where + toHtmlRaw = L.toHtml + toHtml (HtmlPage x) = L.doctypehtml_ $ do + L.head_ $ do + L.meta_ [L.charset_ "utf-8"] + jsRef "/static/ibb.js" + cssRef "/css/main.css" + L.body_ $ do + page + where + page = L.toHtml x + jsRef href = L.with + (L.script_ mempty) + [ makeAttribute "src" href + , makeAttribute "type" "text/javascript" + , makeAttribute "async" mempty + , makeAttribute "defer" mempty + ] + cssRef href = L.with + (L.link_ mempty) + [L.rel_ "stylesheet", L.type_ "text/css", L.href_ href] + +type ServerRoutes = ToServerRoutes AppRoutes HtmlPage Action + +handle404 :: Application +handle404 _ respond = + respond + $ responseLBS status404 [("Content-Type", "text/html")] + $ renderBS + $ toHtml + $ HtmlPage + $ notfound + +newtype CSS = CSS + { unCSS :: Text + } + +instance MimeRender CSS Text where + mimeRender _ = Lazy.encodeUtf8 . Lazy.fromStrict + +instance Accept CSS where + contentType _ = "text" // "css" /: ("charset", "utf-8") + +type CssRoute = "css" :> "main.css" :> Get '[CSS] Text + +type Routes + = "static" + :> + Raw + :<|> + CssRoute + :<|> + ServerRoutes + :<|> + "api" + :> + ApiRoutes + :<|> + Raw + +cssHandlers :: Server CssRoute +cssHandlers = return . Lazy.toStrict . Clay.render $ Look.main + +app :: [Char] -> AcidState Keep.IbbKeep -> Application +app staticDir keep = + serve (Proxy @Routes) + $ static + :<|> cssHandlers + :<|> serverHandlers + :<|> apiHandlers keep + :<|> Tagged handle404 + where static = serveDirectoryWith (defaultWebAppSettings $ staticDir) + +type ApiRoutes = "people" :> Get '[JSON] [Person] + +serverHandlers :: Server ServerRoutes +serverHandlers = homeHandler + where + send f u = pure $ HtmlPage $ f Model { uri = u, people = NotAsked } + homeHandler = send home goHome + +-- | for now we just have one api endpoint, which returns all the people +apiHandlers :: AcidState Keep.IbbKeep -> Server ApiRoutes +apiHandlers keep = Acid.query' keep $ Keep.GetPeople 20 diff --git a/Biz/Ibb/service.nix b/Biz/Ibb/service.nix new file mode 100644 index 0000000..f9d0f36 --- /dev/null +++ b/Biz/Ibb/service.nix @@ -0,0 +1,42 @@ +{ options +, lib +, config +, pkgs +, modulesPath +}: + +let + cfg = config.services.ibb; +in +{ + options.services.ibb = { + enable = lib.mkEnableOption "Enable the IBB service"; + port = lib.mkOption { + type = lib.types.string; + default = "3000"; + description = '' + The port on which IBB will listen for + incoming HTTP traffic. + ''; + }; + }; + config = lib.mkIf cfg.enable { + systemd.services.ibb = { + path = with pkgs; [ ibb bash ]; + wantedBy = [ "multi-user.target" ]; + script = '' + PORT=${cfg.port} ./bin/ibb + ''; + description = '' + Influenced By Books website + ''; + serviceConfig = { + WorkingDirectory = pkgs.ibb; + KillSignal = "INT"; + Type = "simple"; + Restart = "on-abort"; + RestartSec = "10"; + }; + }; + }; +} |