The code and data behind xeiaso.net
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

job history: even more

Signed-off-by: Xe <me@christine.website>

Xe 15a130cc 9f977b38

+518 -121
+2 -33
config.dhall
··· 1 - let Person = ./dhall/types/Person.dhall 2 - 3 - let Author = ./dhall/types/Author.dhall 4 - 5 - let Job = ./dhall/types/Job.dhall 6 - 7 - let defaultPort = env:PORT ? 3030 8 - 9 - let defaultWebMentionEndpoint = 10 - env:WEBMENTION_ENDPOINT 11 - ? "https://mi.within.website/api/webmention/accept" 1 + let xesite = ./dhall/types/package.dhall 12 2 13 - let Config = 14 - { Type = 15 - { signalboost : List Person.Type 16 - , authors : List Author.Type 17 - , port : Natural 18 - , clackSet : List Text 19 - , resumeFname : Text 20 - , webMentionEndpoint : Text 21 - , miToken : Text 22 - , jobHistory : List Job.Type 23 - } 24 - , default = 25 - { signalboost = [] : List Person.Type 26 - , authors = [] : List Author.Type 27 - , port = defaultPort 28 - , clackSet = [ "Ashlynn" ] 29 - , resumeFname = "./static/resume/resume.md" 30 - , webMentionEndpoint = defaultWebMentionEndpoint 31 - , miToken = "${env:MI_TOKEN as Text ? ""}" 32 - , jobHistory = [] : List Job.Type 33 - } 34 - } 3 + let Config = xesite.Config 35 4 36 5 in Config::{ 37 6 , signalboost = ./dhall/signalboost.dhall
-1
dhall/authors.dhall
··· 14 14 , handle = "Heartmender" 15 15 , picUrl = Some 16 16 "https://cdn.christine.website/file/christine-static/img/UPRcp1pO_400x400.jpg" 17 - , link = Some "https://heartmender.writeas.com" 18 17 , twitter = Some "BeJustFine" 19 18 , inSystem = True 20 19 }
+229 -19
dhall/jobHistory.dhall
··· 1 - let Job = ./types/Job.dhall 1 + let xesite = ./types/package.dhall 2 2 3 - let Salary = ./types/Salary.dhall 3 + let Job = xesite.Job 4 + 5 + let Salary = xesite.Salary 6 + 7 + let Stock = xesite.Stock 8 + 9 + let StockKind = xesite.StockKind 10 + 11 + let Company = xesite.Company 12 + 13 + let Location = xesite.Location 4 14 5 15 let annual = \(rate : Natural) -> Salary::{ amount = rate } 6 16 7 17 let hourly = \(rate : Natural) -> Salary::{ amount = rate, per = "hour" } 8 18 9 19 let annualCAD = \(rate : Natural) -> Salary::{ amount = rate, currency = "CAD" } 20 + 21 + let mercerIsland = 22 + Location::{ 23 + , city = "Mercer Island" 24 + , stateOrProvince = "WA" 25 + , country = "USA" 26 + } 27 + 28 + let bellevue = mercerIsland // { city = "Bellevue" } 29 + 30 + let mountainView = 31 + Location::{ 32 + , city = "Mountain View" 33 + , stateOrProvince = "CA" 34 + , country = "USA" 35 + , remote = False 36 + } 37 + 38 + let sf = mountainView // { city = "San Fransisco" } 39 + 40 + let montreal = 41 + Location::{ 42 + , city = "Montreal" 43 + , stateOrProvince = "QC" 44 + , country = "CAN" 45 + , remote = False 46 + } 47 + 48 + let ottawa = 49 + Location::{ city = "Ottawa", stateOrProvince = "ON", country = "CAN" } 50 + 51 + let imvu = 52 + Company::{ 53 + , name = "IMVU" 54 + , url = Some "https://imvu.com" 55 + , tagline = 56 + "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." 57 + , location = mountainView // { city = "Redwood City" } 58 + } 59 + 60 + let tailscale = 61 + Company::{ 62 + , name = "Tailscale" 63 + , url = Some "https://tailscale.com" 64 + , tagline = 65 + "a zero config VPN for building secure networks. Install on any device in minutes. Remote access from any network or physical location." 66 + , location = ottawa // { city = "Toronto" } 67 + } 10 68 11 69 in [ Job::{ 12 - , company = "Symplicity" 70 + , company = Company::{ 71 + , name = "Symplicity" 72 + , tagline = 73 + "a company that provides students with the tools and connections they need to enhance their employability while preparing to succeed in today's job market." 74 + , url = Some "https://www.symplicity.com" 75 + , location = Location::{ 76 + , city = "Arlington" 77 + , stateOrProvince = "VA" 78 + , country = "USA" 79 + , remote = False 80 + } 81 + } 13 82 , title = "Junior Systems Administrator" 14 83 , startDate = "2013-11-11" 15 84 , endDate = Some "2014-01-06" 16 85 , daysWorked = Some 56 17 86 , salary = annual 50000 18 87 , leaveReason = Some "terminated" 88 + , locations = 89 + [ Location::{ 90 + , city = "Arlington" 91 + , stateOrProvince = "VA" 92 + , country = "USA" 93 + , remote = False 94 + } 95 + ] 96 + , highlights = [ "Python message queue processing" ] 97 + , hideFromResume = True 19 98 } 20 99 , Job::{ 21 - , company = "OpDemand" 100 + , company = Company::{ 101 + , name = "OpDemand" 102 + , defunct = True 103 + , tagline = 104 + "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." 105 + , location = Location::{ 106 + , city = "Boulder" 107 + , stateOrProvince = "CO" 108 + , country = "USA" 109 + } 110 + } 22 111 , title = "Software Engineering Intern" 23 112 , startDate = "2014-07-14" 24 113 , endDate = Some "2014-08-27" ··· 26 115 , daysBetween = Some 189 27 116 , salary = annual 35000 28 117 , leaveReason = Some "terminated" 118 + , locations = [ mercerIsland ] 119 + , highlights = 120 + [ "Built new base image for Deis components" 121 + , "Research and development on a new builder component" 122 + ] 123 + , hideFromResume = True 29 124 } 30 125 , Job::{ 31 - , company = "Crowdflower (contract)" 126 + , company = Company::{ 127 + , name = "Appen" 128 + , url = Some "https://appen.com/" 129 + , tagline = 130 + "is a company that uses crowdsourcing to have its customers submit tasks to be done, similar to Amazon's Mechanical Turk." 131 + , location = mountainView // { city = "San Francisco", remote = True } 132 + } 32 133 , title = "Consultant" 134 + , contract = True 33 135 , startDate = "2014-09-17" 34 136 , endDate = Some "2014-10-15" 35 137 , daysWorked = Some 28 36 138 , daysBetween = Some 21 37 139 , salary = hourly 90 38 140 , leaveReason = Some "contract not renewed" 141 + , locations = [ mercerIsland ] 142 + , highlights = 143 + [ "Research and development on scalable Linux deployments on AWS via CoreOS and Docker" 144 + , "Development of in-house tools to speed instance creation" 145 + , "Laid groundwork on the creation and use of better tools for managing large clusters of CoreOS and Fleet machines" 146 + ] 39 147 } 40 148 , Job::{ 41 - , company = "VTCSecure (contract)" 149 + , company = Company::{ 150 + , name = "VTCSecure" 151 + , url = Some "https://www.vtcsecure.com/" 152 + , tagline = 153 + "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." 154 + , location = Location::{ 155 + , city = "Clearwater" 156 + , stateOrProvince = "FL" 157 + , country = "USA" 158 + } 159 + } 42 160 , title = "Consultant" 161 + , contract = True 43 162 , startDate = "2014-10-27" 44 163 , endDate = Some "2015-02-09" 45 164 , daysWorked = Some 105 46 165 , daysBetween = Some 12 47 166 , salary = hourly 90 48 167 , leaveReason = Some "contract not renewed" 168 + , locations = [ mercerIsland ] 169 + , highlights = 170 + [ "Started groundwork for a dynamically scalable infrastructure on a project for helping the blind see things" 171 + , "Developed a prototype of a new website for VTCSecure" 172 + , "Education on best practices using Docker and CoreOS" 173 + , "Learning Freeswitch" 174 + ] 49 175 } 50 176 , Job::{ 51 - , company = "IMVU" 177 + , company = imvu 52 178 , title = "Site Reliability Engineer" 53 179 , startDate = "2015-03-30" 54 180 , endDate = Some "2016-03-07" 55 181 , daysWorked = Some 343 56 182 , daysBetween = Some 49 57 - , salary = annual 125000 183 + , salary = annual 125000 // { stock = Some Stock::{ amount = 20000 } } 58 184 , leaveReason = Some "demoted" 185 + , locations = [ mountainView ] 186 + , highlights = 187 + [ "Wrote up technical designs" 188 + , "Implemented technical designs on an over 800 machine cluster" 189 + , "Continuous learning of a lot of very powerful systems and improving upon them when it is needed" 190 + ] 59 191 } 60 192 , Job::{ 61 - , company = "IMVU" 193 + , company = imvu 62 194 , title = "Systems Administrator" 63 195 , startDate = "2016-03-08" 64 196 , endDate = Some "2016-04-01" ··· 66 198 , daysBetween = Some 1 67 199 , salary = annual 105000 68 200 , leaveReason = Some "quit" 201 + , locations = [ mountainView // { city = "Redwood City" } ] 69 202 } 70 203 , Job::{ 71 - , company = "Pure Storage" 204 + , company = Company::{ 205 + , name = "Pure Storage" 206 + , url = Some "https://www.purestorage.com/" 207 + , tagline = 208 + "a Mountain View, California-based enterprise data flash storage company founded in 2009. It is traded on the NYSE (PSTG)." 209 + , location = mountainView 210 + } 72 211 , title = "Member of Technical Staff" 73 212 , startDate = "2016-04-04" 74 213 , endDate = Some "2016-08-03" 75 214 , daysWorked = Some 121 76 215 , daysBetween = Some 3 77 - , salary = annual 135000 216 + , salary = 217 + annual 135000 218 + // { stock = Some Stock::{ 219 + , amount = 5000 220 + , liquid = True 221 + , kind = StockKind.Grant 222 + } 223 + } 78 224 , leaveReason = Some "quit" 225 + , locations = [ mountainView ] 226 + , highlights = [ "Python 2 code maintenance", "Working with Foone" ] 79 227 } 80 228 , Job::{ 81 - , company = "Backplane.io (defunct)" 229 + , company = Company::{ 230 + , name = "Backplane.io" 231 + , defunct = True 232 + , location = sf 233 + } 82 234 , title = "Software Engineer" 83 235 , startDate = "2016-08-24" 84 236 , endDate = Some "2016-11-22" 85 237 , daysWorked = Some 90 86 238 , daysBetween = Some 21 87 - , salary = annual 105000 239 + , salary = annual 105000 // { stock = Some Stock::{ amount = 85000 } } 88 240 , leaveReason = Some "terminated" 241 + , locations = [ sf ] 242 + , highlights = 243 + [ "Performance monitoring of production servers" 244 + , "Continuous deployment and development in Go" 245 + , "Learning a lot about HTTP/2 and load balancing" 246 + ] 89 247 } 90 248 , Job::{ 91 - , company = "Heroku (contract)" 249 + , company = Company::{ 250 + , name = "MBO Partners (Heroku)" 251 + , tagline = "a staffing agency used to contract me for Heroku." 252 + , location = Location::{ 253 + , city = "Herndon" 254 + , stateOrProvince = "VA" 255 + , country = "USA" 256 + } 257 + } 92 258 , title = "Consultant" 259 + , contract = True 93 260 , startDate = "2017-02-13" 94 261 , endDate = Some "2017-11-13" 95 262 , daysWorked = Some 273 96 263 , daysBetween = Some 83 97 264 , salary = hourly 120 98 265 , leaveReason = Some "hired" 266 + , locations = [ mountainView ] 99 267 } 100 268 , Job::{ 101 - , company = "Heroku" 269 + , company = Company::{ 270 + , name = "Heroku" 271 + , url = Some "https://heroku.com" 272 + , tagline = 273 + "a cloud Platform-as-a-Service (PaaS) that created the term 'platform as a service'. Heroku currently supports several programming languages that are commonly used on the web. Heroku, one of the first cloud platforms, has been in development since June 2007, when it supported only the Ruby programming language, but now supports Java, Node.js, Scala, Clojure, Python, PHP, and Go." 274 + , location = sf 275 + } 102 276 , title = "Senior Software Engineer" 103 277 , startDate = "2017-11-13" 104 278 , endDate = Some "2019-03-08" ··· 106 280 , daysBetween = Some 0 107 281 , salary = annual 150000 108 282 , leaveReason = Some "quit" 283 + , locations = [ mountainView, bellevue ] 284 + , highlights = 285 + [ "JVM Application Metrics" 286 + , "Go Runtime Metrics Agent" 287 + , "Other backend fixes and improvements on Threshold Autoscaling and Threshold Alerting" 288 + , "Public-facing blogpost writing" 289 + ] 109 290 } 110 291 , Job::{ 111 - , company = "Lightspeed POS" 292 + , company = Company::{ 293 + , name = "Lightspeed POS" 294 + , url = Some "https://lightspeedhq.com" 295 + , tagline = 296 + "a provider of retail, ecommerce and point-of-sale solutions for small and medium scale businesses." 297 + , location = montreal 298 + } 112 299 , title = "Expert principal en fiabilité du site" 113 300 , startDate = "2019-05-06" 114 301 , endDate = Some "2020-11-27" 115 302 , daysWorked = Some 540 116 303 , daysBetween = Some 48 117 - , salary = annualCAD 115000 304 + , salary = 305 + annualCAD 115000 306 + // { stock = Some Stock::{ amount = 7500, liquid = True } } 118 307 , leaveReason = Some "quit" 308 + , locations = [ montreal ] 309 + , highlights = 310 + [ "Migration from cloud to cloud" 311 + , "Work on the cloud platform initiative" 312 + , "Crafting reliable infrastructure for clients of customers" 313 + , "Creation of an internally consistent and extensible command line interface for internal tooling" 314 + ] 119 315 } 120 316 , Job::{ 121 - , company = "Tailscale" 317 + , company = tailscale 122 318 , title = "Software Designer" 123 319 , startDate = "2020-12-14" 124 320 , endDate = Some "2022-03-01" ··· 126 322 , daysBetween = Some 0 127 323 , salary = annualCAD 135000 128 324 , leaveReason = Some "raise" 325 + , locations = [ montreal // { remote = True }, ottawa ] 326 + , highlights = 327 + [ "Go programming" 328 + , "SQL integrations" 329 + , "Public-facing content writing" 330 + , "Customer support" 331 + ] 129 332 } 130 333 , Job::{ 131 - , company = "Tailscale" 334 + , company = tailscale 132 335 , title = "Archmage of Infrastructure" 133 336 , startDate = "2022-03-01" 134 337 , salary = annualCAD 147150 338 + , locations = [ ottawa ] 339 + , highlights = 340 + [ "The first developer relations person at Tailscale" 341 + , "Public-facing content writing" 342 + , "Public speaking" 343 + , "Developing custom integration solutions and supporting them" 344 + ] 135 345 } 136 346 ]
+17
dhall/types/Company.dhall
··· 1 + let Location = ./Location.dhall 2 + 3 + in { Type = 4 + { name : Text 5 + , url : Optional Text 6 + , tagline : Text 7 + , location : Location.Type 8 + , defunct : Bool 9 + } 10 + , default = 11 + { name = "" 12 + , url = None Text 13 + , tagline = "" 14 + , location = Location::{=} 15 + , defunct = False 16 + } 17 + }
+33
dhall/types/Config.dhall
··· 1 + let Person = ./Person.dhall 2 + 3 + let Author = ./Author.dhall 4 + 5 + let Job = ./Job.dhall 6 + 7 + let defaultPort = env:PORT ? 3030 8 + 9 + let defaultWebMentionEndpoint = 10 + env:WEBMENTION_ENDPOINT 11 + ? "https://mi.within.website/api/webmention/accept" 12 + 13 + in { Type = 14 + { signalboost : List Person.Type 15 + , authors : List Author.Type 16 + , port : Natural 17 + , clackSet : List Text 18 + , resumeFname : Text 19 + , webMentionEndpoint : Text 20 + , miToken : Text 21 + , jobHistory : List Job.Type 22 + } 23 + , default = 24 + { signalboost = [] : List Person.Type 25 + , authors = [] : List Author.Type 26 + , port = defaultPort 27 + , clackSet = [ "Ashlynn" ] 28 + , resumeFname = "./static/resume/resume.md" 29 + , webMentionEndpoint = defaultWebMentionEndpoint 30 + , miToken = "${env:MI_TOKEN as Text ? ""}" 31 + , jobHistory = [] : List Job.Type 32 + } 33 + }
+14 -2
dhall/types/Job.dhall
··· 1 + let Company = ./Company.dhall 2 + 1 3 let Salary = ./Salary.dhall 4 + 5 + let Location = ./Location.dhall 2 6 3 7 in { Type = 4 - { company : Text 8 + { company : Company.Type 5 9 , title : Text 10 + , contract : Bool 6 11 , startDate : Text 7 12 , endDate : Optional Text 8 13 , daysWorked : Optional Natural 9 14 , daysBetween : Optional Natural 10 15 , salary : Salary.Type 11 16 , leaveReason : Optional Text 17 + , locations : List Location.Type 18 + , highlights : List Text 19 + , hideFromResume : Bool 12 20 } 13 21 , default = 14 - { company = "Unknown" 22 + { company = Company::{=} 15 23 , title = "Unknown" 24 + , contract = False 16 25 , startDate = "0000-01-01" 17 26 , endDate = None Text 18 27 , daysWorked = None Natural 19 28 , daysBetween = None Natural 20 29 , salary = Salary::{=} 21 30 , leaveReason = None Text 31 + , locations = [] : List Location.Type 32 + , highlights = [] : List Text 33 + , hideFromResume = False 22 34 } 23 35 }
+3
dhall/types/Location.dhall
··· 1 + { Type = { city : Text, stateOrProvince : Text, country : Text, remote : Bool } 2 + , default = { remote = True, city = "", stateOrProvince = "", country = "CAN" } 3 + }
+11 -3
dhall/types/Salary.dhall
··· 1 - { Type = { amount : Natural, currency : Text, per : Text } 2 - , default = { amount = 0, currency = "USD", per = "year" } 3 - } 1 + let Stock = ./Stock.dhall 2 + 3 + in { Type = 4 + { amount : Natural 5 + , currency : Text 6 + , per : Text 7 + , stock : Optional Stock.Type 8 + } 9 + , default = 10 + { amount = 0, currency = "USD", per = "year", stock = None Stock.Type } 11 + }
+17
dhall/types/Stock.dhall
··· 1 + let StockKind = ./StockKind.dhall 2 + 3 + in { Type = 4 + { kind : StockKind 5 + , amount : Natural 6 + , liquid : Bool 7 + , vestingYears : Natural 8 + , cliffYears : Natural 9 + } 10 + , default = 11 + { kind = StockKind.Options 12 + , amount = 0 13 + , liquid = False 14 + , vestingYears = 4 15 + , cliffYears = 1 16 + } 17 + }
+1
dhall/types/StockKind.dhall
··· 1 + < Grant | Options >
+10
dhall/types/package.dhall
··· 1 + { Author = ./Author.dhall 2 + , Company = ./Company.dhall 3 + , Config = ./Config.dhall 4 + , Job = ./Job.dhall 5 + , Location = ./Location.dhall 6 + , Person = ./Person.dhall 7 + , Salary = ./Salary.dhall 8 + , Stock = ./Stock.dhall 9 + , StockKind = ./StockKind.dhall 10 + }
+178
src/app/config.rs
··· 1 + use crate::signalboost::Person; 2 + use maud::{html, Markup}; 3 + use serde::{Deserialize, Serialize}; 4 + use std::{ 5 + fmt::{self, Display}, 6 + path::PathBuf, 7 + }; 8 + 9 + #[derive(Clone, Deserialize, Default)] 10 + pub struct Config { 11 + pub signalboost: Vec<Person>, 12 + pub authors: Vec<Author>, 13 + pub port: u16, 14 + #[serde(rename = "clackSet")] 15 + pub clack_set: Vec<String>, 16 + #[serde(rename = "resumeFname")] 17 + pub resume_fname: PathBuf, 18 + #[serde(rename = "miToken")] 19 + pub mi_token: String, 20 + #[serde(rename = "jobHistory")] 21 + pub job_history: Vec<Job>, 22 + } 23 + 24 + #[derive(Clone, Deserialize, Serialize)] 25 + pub enum StockKind { 26 + Grant, 27 + Options, 28 + } 29 + 30 + impl Default for StockKind { 31 + fn default() -> Self { 32 + StockKind::Options 33 + } 34 + } 35 + 36 + #[derive(Clone, Deserialize, Serialize, Default)] 37 + pub struct Author { 38 + pub name: String, 39 + pub handle: String, 40 + #[serde(rename = "picUrl")] 41 + pub pic_url: Option<String>, 42 + pub link: Option<String>, 43 + pub twitter: Option<String>, 44 + pub default: bool, 45 + #[serde(rename = "inSystem")] 46 + pub in_system: bool, 47 + } 48 + 49 + #[derive(Clone, Deserialize, Serialize, Default)] 50 + pub struct Stock { 51 + pub amount: i32, 52 + #[serde(rename = "cliffYears")] 53 + pub cliff_years: i32, 54 + pub kind: StockKind, 55 + pub liquid: bool, 56 + #[serde(rename = "vestingYears")] 57 + pub vesting_years: i32, 58 + } 59 + 60 + #[derive(Clone, Deserialize, Serialize, Default)] 61 + pub struct Location { 62 + pub city: String, 63 + #[serde(rename = "stateOrProvince")] 64 + pub state_or_province: String, 65 + pub country: String, 66 + pub remote: bool, 67 + } 68 + 69 + #[derive(Clone, Deserialize, Serialize, Default)] 70 + pub struct Salary { 71 + pub amount: i32, 72 + pub per: String, 73 + pub currency: String, 74 + pub stock: Option<Stock>, 75 + } 76 + 77 + impl Display for Salary { 78 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 79 + write!(f, "{}${}/{}", self.currency, self.amount, self.per) 80 + } 81 + } 82 + 83 + impl Salary { 84 + pub fn html(&self) -> Markup { 85 + if self.stock.is_none() { 86 + return html! { (self) }; 87 + } 88 + 89 + let stock = self.stock.as_ref().unwrap(); 90 + html! { 91 + details { 92 + summary { 93 + (self) 94 + } 95 + 96 + p{ 97 + (stock.amount) 98 + " " 99 + @if stock.liquid { 100 + "liquid" 101 + } 102 + " " 103 + @match stock.kind { 104 + StockKind::Options => { 105 + "options" 106 + }, 107 + StockKind::Grant => { 108 + "granted shares" 109 + } 110 + } 111 + ". Vesting for " 112 + (stock.vesting_years) 113 + " " 114 + @if stock.vesting_years == 1 { 115 + "year" 116 + } @else { 117 + "years" 118 + } 119 + " " 120 + " with a cliff of " 121 + (stock.cliff_years) 122 + " " 123 + @if stock.cliff_years == 1 { 124 + "year" 125 + } @else { 126 + "years" 127 + } 128 + "." 129 + } 130 + } 131 + } 132 + } 133 + } 134 + 135 + #[derive(Clone, Deserialize, Serialize, Default)] 136 + pub struct Job { 137 + pub company: Company, 138 + pub title: String, 139 + #[serde(rename = "startDate")] 140 + pub start_date: String, 141 + #[serde(rename = "endDate")] 142 + pub end_date: Option<String>, 143 + #[serde(rename = "daysWorked")] 144 + pub days_worked: Option<i32>, 145 + #[serde(rename = "daysBetween")] 146 + pub days_between: Option<i32>, 147 + pub salary: Salary, 148 + #[serde(rename = "leaveReason")] 149 + pub leave_reason: Option<String>, 150 + pub locations: Vec<Location>, 151 + pub highlights: Vec<String>, 152 + #[serde(rename = "hideFromResume")] 153 + pub hide_from_resume: bool, 154 + } 155 + 156 + #[derive(Clone, Deserialize, Serialize, Default)] 157 + pub struct Company { 158 + pub name: String, 159 + pub url: Option<String>, 160 + pub tagline: String, 161 + pub location: Location, 162 + pub defunct: bool, 163 + } 164 + 165 + impl Job { 166 + pub fn pay_history_row(&self) -> Markup { 167 + html! { 168 + tr { 169 + td { (self.title) } 170 + td { (self.start_date) } 171 + td { (self.end_date.as_ref().unwrap_or(&"current".to_string())) } 172 + td { (if self.days_worked.is_some() { self.days_worked.as_ref().unwrap().to_string() } else { "n/a".to_string() }) } 173 + td { (self.salary.html()) } 174 + td { (self.leave_reason.as_ref().unwrap_or(&"n/a".to_string())) } 175 + } 176 + } 177 + } 178 + }
+3 -63
src/app/mod.rs
··· 1 1 use crate::{post::Post, signalboost::Person}; 2 2 use chrono::prelude::*; 3 3 use color_eyre::eyre::Result; 4 - use maud::{html, Markup}; 5 - use serde::{Deserialize, Serialize}; 6 - use std::{ 7 - fmt::{self, Display}, 8 - fs, 9 - path::PathBuf, 10 - sync::Arc, 11 - }; 4 + use std::{fs, path::PathBuf, sync::Arc}; 12 5 use tracing::{error, instrument}; 13 6 7 + pub mod config; 14 8 pub mod markdown; 15 9 pub mod poke; 16 10 17 - #[derive(Clone, Deserialize, Default)] 18 - pub struct Config { 19 - pub(crate) signalboost: Vec<Person>, 20 - #[serde(rename = "resumeFname")] 21 - pub(crate) resume_fname: PathBuf, 22 - #[serde(rename = "miToken")] 23 - pub(crate) mi_token: String, 24 - #[serde(rename = "jobHistory")] 25 - pub(crate) job_history: Vec<Job>, 26 - } 27 - 28 - #[derive(Clone, Deserialize, Serialize, Default)] 29 - pub struct Salary { 30 - pub amount: i32, 31 - pub per: String, 32 - pub currency: String, 33 - } 34 - 35 - impl Display for Salary { 36 - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 37 - write!(f, "{}${}/{}", self.currency, self.amount, self.per) 38 - } 39 - } 40 - 41 - #[derive(Clone, Deserialize, Serialize, Default)] 42 - pub struct Job { 43 - pub company: String, 44 - pub title: String, 45 - #[serde(rename = "startDate")] 46 - pub start_date: String, 47 - #[serde(rename = "endDate")] 48 - pub end_date: Option<String>, 49 - #[serde(rename = "daysWorked")] 50 - pub days_worked: Option<i32>, 51 - #[serde(rename = "daysBetween")] 52 - pub days_between: Option<i32>, 53 - pub salary: Salary, 54 - #[serde(rename = "leaveReason")] 55 - pub leave_reason: Option<String>, 56 - } 57 - 58 - impl Job { 59 - pub fn pay_history_row(&self) -> Markup { 60 - html! { 61 - tr { 62 - td { (self.title) } 63 - td { (self.start_date) } 64 - td { (self.end_date.as_ref().unwrap_or(&"current".to_string())) } 65 - td { (if self.days_worked.is_some() { self.days_worked.as_ref().unwrap().to_string() } else { "n/a".to_string() }) } 66 - td { (self.salary) } 67 - td { (self.leave_reason.as_ref().unwrap_or(&"n/a".to_string())) } 68 - } 69 - } 70 - } 71 - } 11 + pub use config::*; 72 12 73 13 #[instrument] 74 14 async fn patrons() -> Result<Option<patreon::Users>> {