From dba3ae46f8cbf9eca60e324447e7715744c2130b Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Sun, 18 Dec 2016 08:51:32 -0800 Subject: [PATCH] view my resume --- backend/christine.website/main.go | 19 ++++- frontend/src/BlogIndex.purs | 4 +- frontend/src/Layout.purs | 21 +++-- frontend/src/Resume.purs | 66 +++++++++++++++ frontend/src/Routes.purs | 2 + static/resume/resume.md | 132 ++++++++++++++++++++++++++++++ 6 files changed, 235 insertions(+), 9 deletions(-) create mode 100644 frontend/src/Resume.purs create mode 100644 static/resume/resume.md 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