diff --git a/backend/christine.website/main.go b/backend/christine.website/main.go
index d0c6d3f..a6a29f4 100644
--- a/backend/christine.website/main.go
+++ b/backend/christine.website/main.go
@@ -36,7 +36,10 @@ func (p Posts) Less(i, j int) bool {
}
func (p Posts) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
-var posts Posts
+var (
+ posts Posts
+ rbody string
+)
func init() {
err := filepath.Walk("./blog/", func(path string, info os.FileInfo, err error) error {
@@ -87,6 +90,13 @@ func init() {
}
sort.Sort(sort.Reverse(posts))
+
+ resume, err := ioutil.ReadFile("./static/resume/resume.md")
+ if err != nil {
+ panic(err)
+ }
+
+ rbody = string(resume)
}
func main() {
@@ -109,6 +119,13 @@ func main() {
fail:
http.Error(w, "Not Found", http.StatusNotFound)
})
+ http.HandleFunc("/api/resume", func(w http.ResponseWriter, r *http.Request) {
+ json.NewEncoder(w).Encode(struct {
+ Body string `json:"body"`
+ }{
+ Body: rbody,
+ })
+ })
http.Handle("/dist/", http.FileServer(http.Dir("./frontend/static/")))
http.Handle("/static/", http.FileServer(http.Dir(".")))
http.HandleFunc("/", writeIndexHTML)
diff --git a/frontend/src/BlogIndex.purs b/frontend/src/BlogIndex.purs
index 8083575..1b41dbf 100644
--- a/frontend/src/BlogIndex.purs
+++ b/frontend/src/BlogIndex.purs
@@ -82,5 +82,5 @@ view state =
[]
[ h1 [] [ text "Posts" ]
, documentTitle [ title "Posts - Christine Dodrill" ] []
- , p [] [ text state.status ]
- , div [ className "row" ] $ map post state.posts ]
+ , div [ className "row" ] $ map post state.posts
+ , p [] [ text state.status ] ]
diff --git a/frontend/src/Layout.purs b/frontend/src/Layout.purs
index 1b810aa..da5b55e 100644
--- a/frontend/src/Layout.purs
+++ b/frontend/src/Layout.purs
@@ -3,6 +3,7 @@ module App.Layout where
import App.BlogEntry as BlogEntry
import App.BlogIndex as BlogIndex
import App.Counter as Counter
+import App.Resume as Resume
import App.Routes (Route(..))
import Control.Monad.RWS (state)
import DOM (DOM)
@@ -20,20 +21,23 @@ data Action
= Child (Counter.Action)
| BIChild (BlogIndex.Action)
| BEChild (BlogEntry.Action)
+ | REChild (Resume.Action)
| PageView Route
type State =
{ route :: Route
, count :: Counter.State
, bistate :: BlogIndex.State
- , bestate :: BlogEntry.State }
+ , bestate :: BlogEntry.State
+ , restate :: Resume.State }
init :: State
init =
{ route: NotFound
, count: Counter.init
, bistate: BlogIndex.init
- , bestate: BlogEntry.init }
+ , bestate: BlogEntry.init
+ , restate: Resume.init }
update :: Action -> State -> EffModel State Action (ajax :: AJAX, dom :: DOM)
update (PageView route) state = routeEffects route $ state { route = route }
@@ -43,14 +47,19 @@ update (BIChild action) state = BlogIndex.update action state.bistate
update (BEChild action) state = BlogEntry.update action state.bestate
# mapState (state { bestate = _ })
# mapEffects BEChild
+update (REChild action) state = Resume.update action state.restate
+ # mapState ( state { restate = _ })
+ # mapEffects REChild
update (Child action) state = noEffects $ state { count = Counter.update action state.count }
update _ state = noEffects $ state
routeEffects :: Route -> State -> EffModel State Action (dom :: DOM, ajax :: AJAX)
-routeEffects BlogIndex state = { state: state
+routeEffects (BlogIndex) state = { state: state
, effects: [ pure BlogIndex.RequestPosts ] } # mapEffects BIChild
-routeEffects (BlogPost page) state = { state: state { bestate = BlogEntry.init { name = page } }
- , effects: [ pure BlogEntry.RequestPost ] } # mapEffects BEChild
+routeEffects (Resume) state = { state: state
+ , effects: [ pure Resume.RequestResume ] } # mapEffects REChild
+routeEffects (BlogPost page') state = { state: state { bestate = BlogEntry.init { name = page' } }
+ , effects: [ pure BlogEntry.RequestPost ] } # mapEffects BEChild
routeEffects _ state = noEffects $ state
view :: State -> Html Action
@@ -162,7 +171,7 @@ index =
page :: Route -> State -> Html Action
page NotFound _ = h1 [] [ text "not found" ]
page Home _ = index
-page Resume state = h1 [] [ text "Christine Dodrill" ]
+page Resume state = map REChild $ Resume.view state.restate
page BlogIndex state = map BIChild $ BlogIndex.view state.bistate
page (BlogPost _) state = map BEChild $ BlogEntry.view state.bestate
page ContactPage _ = contact
diff --git a/frontend/src/Resume.purs b/frontend/src/Resume.purs
new file mode 100644
index 0000000..8a23d02
--- /dev/null
+++ b/frontend/src/Resume.purs
@@ -0,0 +1,66 @@
+module App.Resume where
+
+import App.Utils (mdify)
+import Control.Monad.Aff (attempt)
+import DOM (DOM)
+import Data.Argonaut (class DecodeJson, decodeJson, (.?))
+import Data.Either (Either(..), either)
+import Data.Maybe (Maybe(..))
+import Network.HTTP.Affjax (AJAX, get)
+import Prelude (Unit, bind, pure, show, unit, ($), (<>), (<<<))
+import Pux (noEffects, EffModel)
+import Pux.DocumentTitle (documentTitle)
+import Pux.Html (Html, a, div, h1, p, text)
+import Pux.Html.Attributes (href, dangerouslySetInnerHTML, className, id_, title)
+
+data Action = RequestResume
+ | ReceiveResume (Either String Resume)
+
+type State =
+ { status :: String
+ , err :: String
+ , resume :: Maybe Resume }
+
+data Resume = Resume
+ { body :: String }
+
+instance decodeJsonResume :: DecodeJson Resume where
+ decodeJson json = do
+ obj <- decodeJson json
+ body <- obj .? "body"
+ pure $ Resume { body: body }
+
+init :: State
+init =
+ { status: "Loading..."
+ , err: ""
+ , resume: Nothing }
+
+update :: Action -> State -> EffModel State Action (ajax :: AJAX, dom :: DOM)
+update (ReceiveResume (Left err)) state =
+ noEffects $ state { resume = Nothing, status = "Error in fetching resume, please use the plain text link below.", err = err }
+update (ReceiveResume (Right body)) state =
+ noEffects $ state { status = "", err = "", resume = Just body }
+ where
+ got' = Just unit
+update RequestResume state =
+ { state: state
+ , effects: [ do
+ res <- attempt $ get "/api/resume"
+ let decode r = decodeJson r.response :: Either String Resume
+ let resume = either (Left <<< show) decode res
+ pure $ ReceiveResume resume
+ ]
+ }
+
+view :: State -> Html Action
+view { status: status, err: err, resume: resume } =
+ case resume of
+ Nothing -> div [] [ text status, p [] [ text err ] ]
+ (Just (Resume resume')) ->
+ div [ className "row" ]
+ [ documentTitle [ title "Resume - Christine Dodrill" ] []
+ , div [ className "col s8 offset-s2" ]
+ [ p [ className "browser-default", dangerouslySetInnerHTML $ mdify resume'.body ] []
+ , a [ href "/static/resume/resume.md" ] [ text "Plain-text version of this resume here" ], text "." ]
+ ]
diff --git a/frontend/src/Routes.purs b/frontend/src/Routes.purs
index 886e774..7076898 100644
--- a/frontend/src/Routes.purs
+++ b/frontend/src/Routes.purs
@@ -27,3 +27,5 @@ match url = fromMaybe NotFound $ router url $
BlogPost <$> (lit "blog" *> str) <* end
<|>
ContactPage <$ lit "contact" <* end
+ <|>
+ Resume <$ lit "resume" <* end
diff --git a/static/resume/resume.md b/static/resume/resume.md
new file mode 100644
index 0000000..611a710
--- /dev/null
+++ b/static/resume/resume.md
@@ -0,0 +1,132 @@
+# Christine Dodrill
+
+---
+
+> #### Rockstar Hacker, Cloud Architect, Gopher, Haskeller, Container Expert
+> ##### Mountain View, CA [christine.website][homepage] [@theprincessxena][twitter] ![twit][]
+> `Docker`, `Git`, `Haskell`, `Nim`, `Go`, `C`, `CentOS`, `CoreOS`, `IRC`, `Matrix`
+
+---
+> **"** A github power user, constantly learns new things to keep up on what's new in tech.
+
+
+---
+## Experience
+#### Backplane.io - Software Engineer *2016 - 2016*
+`Go`, `Docker`, `docker-compose`, `devops`, `PostgreSQL`
+> [Backplane](https://backplane.io) is an innovative reverse reverse proxy that
+> helps administrators and startups simplify their web application routing.
+>
+> #### Highlights
+>
+> - Performance monitoring of production servers
+> - Continuous deployment and development in Go
+> - Learning a lot about HTTP/2 and load balancing
+
+---
+#### IMVU - Site Reliability Engineer *2015 - 2016*
+`Ubuntu Server`, `CFEngine`, `Haskell`, `Go`, `Perl`, `Nginx`, `JunOS`, `Ceph`, `MySQL`, `Redis`, `Memcached`, `PHP`, `Erlang`
+> IMVU, inc is a company whose mission is to help people find and communicate
+> with eachother. Their main product is a 3D avatar-based chat client and its
+> surrounding infrastructure allowing creators to make content for the avatars
+> to wear.
+>
+> #### Highlights
+>
+> - Wrote up technical designs
+> - Implemented technical designs on an over 800 machine cluster
+> - Continuous learning of a lot of very powerful systems and improving upon them
+> when it is needed
+
+---
+#### VTCSecure - Deis Consultant (contract) *2014 - 2015*
+`Deis`, `Docker`, `CoreOS`, `Go`, `Freeswitch`
+> VTCSecure is a company dedicated to helping with custom and standard
+> audio/video conferencing solutions. They specialize in helping the deaf and
+> blind communicate over today's infrastructure without any trouble on their end.
+>
+> #### Highlights
+>
+> - Started groundwork for a dynamically scalable infrastructure on a project for helping the blind see things
+> - Developed a prototype of a new website for VTCSecure
+> - Education on best practices using Docker and CoreOS
+> - Learning Freeswitch
+
+---
+#### Crowdflower - Deis Consultant (Contract) *2014 - 2014*
+`Ruby`, `Rails`, `Chef`, `CoreOS`, `Docker`, `Deis`
+> Crowdflower is a company that uses crowdsourcing to have its customers submit
+> tasks to be done, similar to Amazon's Mechanical Turk. CrowdFlower has over 50
+> labor channel partners, and its network has more than 5 million contributors
+> worldwide.
+>
+> #### Highlights
+>
+> - Research and development on scalable Linux deployments on AWS via CoreOS and
+> Docker
+> - Development of in-house tools to speed instance creation
+> - Laid groundwork on the creation and use of better tools for managing large
+> clusters of CoreOS and Fleet machines
+
+---
+#### OpDemand - Software Engineering Intern *2014 - 2014*
+`Deis`, `CoreOS`, `Go`, `Docker`
+> OpDemand is the company behind the open source project Deis, a distributed
+> platform-as-a-service (PaaS) designed from the ground up to emulate Heroku but
+> on privately owned servers.
+>
+> #### Highlights
+>
+> - Built new base image for Deis components
+> - Research and development on a new builder component
+
+---
+## Open Source
+#### [Elemental-IRCd](http://elemental-ircd.com)
+A scalable RFC compliant IRCv3 enabled IRC server for personal and professional use.
+
+#### Accomplishments
+
+* Automated testing via [Travis](https://travis-ci.org/Elemental-IRCd/elemental-ircd)
+* Community management via [Github](https://github.com/elemental-ircd/elemental-ircd)
+
+Elemental is currently in use in production on several networks, totaling 800-1000
+users per day with spikes of up to 50,000 on special events.
+
+---
+#### [Tetra](https://github.com/Xe/Tetra)
+A modern IRC services platform for TS6 IRC daemons.
+
+#### Accomplishments
+
+* Parallel, safe execution of handlers and scripts
+* Moonscript -> Lua transpiling support
+* A clean, declarative domain-specific language for declaring features or bot commands:
+
+```
+Command "PING", ->
+ "PONG"
+```
+
+This will create a command named "PING" that will return "PONG" to the user when it is used.
+
+---
+#### [PonyAPI](https://github.com/Xe/ponyapi)
+A simple API for information on episodes of My Little Pony: Friendship is Magic written in Nim to be run inside a container.
+
+All data is loaded into ram and there are no usage limits as long as you agree to not take down the server it is running on.
+
+---
+#### [Professional Projects](https://github.com/Xe)
+Projects here will be of a more professional nature (save a few here and there).
+
+---
+## Writing
+
+> Articles listed below will be either personal or professional and do not reflect the views of any company or group I am affiliated with. The writing is my own.
+#### [My Blog](https://christine.website/blog) *@christine.website/blog*
+
+---
+[homepage]: https://christine.website
+[twitter]: https://twitter.com/theprincessxena
+[twit]: http://cdn-careers.sstatic.net/careers/Img/icon-twitter.png?v=b1bd58ad2034