job history: even more

Signed-off-by: Xe <me@christine.website>
This commit is contained in:
Cadey Ratio 2022-06-20 12:47:11 +00:00
parent 9f977b3882
commit 15a130cc3d
13 changed files with 518 additions and 121 deletions

View File

@ -1,37 +1,6 @@
let Person = ./dhall/types/Person.dhall let xesite = ./dhall/types/package.dhall
let Author = ./dhall/types/Author.dhall let Config = xesite.Config
let Job = ./dhall/types/Job.dhall
let defaultPort = env:PORT ? 3030
let defaultWebMentionEndpoint =
env:WEBMENTION_ENDPOINT
? "https://mi.within.website/api/webmention/accept"
let Config =
{ Type =
{ signalboost : List Person.Type
, authors : List Author.Type
, port : Natural
, clackSet : List Text
, resumeFname : Text
, webMentionEndpoint : Text
, miToken : Text
, jobHistory : List Job.Type
}
, default =
{ signalboost = [] : List Person.Type
, authors = [] : List Author.Type
, port = defaultPort
, clackSet = [ "Ashlynn" ]
, resumeFname = "./static/resume/resume.md"
, webMentionEndpoint = defaultWebMentionEndpoint
, miToken = "${env:MI_TOKEN as Text ? ""}"
, jobHistory = [] : List Job.Type
}
}
in Config::{ in Config::{
, signalboost = ./dhall/signalboost.dhall , signalboost = ./dhall/signalboost.dhall

View File

@ -14,7 +14,6 @@ in [ Author::{
, handle = "Heartmender" , handle = "Heartmender"
, picUrl = Some , picUrl = Some
"https://cdn.christine.website/file/christine-static/img/UPRcp1pO_400x400.jpg" "https://cdn.christine.website/file/christine-static/img/UPRcp1pO_400x400.jpg"
, link = Some "https://heartmender.writeas.com"
, twitter = Some "BeJustFine" , twitter = Some "BeJustFine"
, inSystem = True , inSystem = True
} }

View File

@ -1,6 +1,16 @@
let Job = ./types/Job.dhall let xesite = ./types/package.dhall
let Salary = ./types/Salary.dhall let Job = xesite.Job
let Salary = xesite.Salary
let Stock = xesite.Stock
let StockKind = xesite.StockKind
let Company = xesite.Company
let Location = xesite.Location
let annual = \(rate : Natural) -> Salary::{ amount = rate } let annual = \(rate : Natural) -> Salary::{ amount = rate }
@ -8,17 +18,96 @@ let hourly = \(rate : Natural) -> Salary::{ amount = rate, per = "hour" }
let annualCAD = \(rate : Natural) -> Salary::{ amount = rate, currency = "CAD" } let annualCAD = \(rate : Natural) -> Salary::{ amount = rate, currency = "CAD" }
let mercerIsland =
Location::{
, city = "Mercer Island"
, stateOrProvince = "WA"
, country = "USA"
}
let bellevue = mercerIsland // { city = "Bellevue" }
let mountainView =
Location::{
, city = "Mountain View"
, stateOrProvince = "CA"
, country = "USA"
, remote = False
}
let sf = mountainView // { city = "San Fransisco" }
let montreal =
Location::{
, city = "Montreal"
, stateOrProvince = "QC"
, country = "CAN"
, remote = False
}
let ottawa =
Location::{ city = "Ottawa", stateOrProvince = "ON", country = "CAN" }
let imvu =
Company::{
, name = "IMVU"
, url = Some "https://imvu.com"
, tagline =
"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."
, location = mountainView // { city = "Redwood City" }
}
let tailscale =
Company::{
, name = "Tailscale"
, url = Some "https://tailscale.com"
, tagline =
"a zero config VPN for building secure networks. Install on any device in minutes. Remote access from any network or physical location."
, location = ottawa // { city = "Toronto" }
}
in [ Job::{ in [ Job::{
, company = "Symplicity" , company = Company::{
, name = "Symplicity"
, tagline =
"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."
, url = Some "https://www.symplicity.com"
, location = Location::{
, city = "Arlington"
, stateOrProvince = "VA"
, country = "USA"
, remote = False
}
}
, title = "Junior Systems Administrator" , title = "Junior Systems Administrator"
, startDate = "2013-11-11" , startDate = "2013-11-11"
, endDate = Some "2014-01-06" , endDate = Some "2014-01-06"
, daysWorked = Some 56 , daysWorked = Some 56
, salary = annual 50000 , salary = annual 50000
, leaveReason = Some "terminated" , leaveReason = Some "terminated"
, locations =
[ Location::{
, city = "Arlington"
, stateOrProvince = "VA"
, country = "USA"
, remote = False
}
]
, highlights = [ "Python message queue processing" ]
, hideFromResume = True
} }
, Job::{ , Job::{
, company = "OpDemand" , company = Company::{
, name = "OpDemand"
, defunct = True
, tagline =
"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."
, location = Location::{
, city = "Boulder"
, stateOrProvince = "CO"
, country = "USA"
}
}
, title = "Software Engineering Intern" , title = "Software Engineering Intern"
, startDate = "2014-07-14" , startDate = "2014-07-14"
, endDate = Some "2014-08-27" , endDate = Some "2014-08-27"
@ -26,39 +115,82 @@ in [ Job::{
, daysBetween = Some 189 , daysBetween = Some 189
, salary = annual 35000 , salary = annual 35000
, leaveReason = Some "terminated" , leaveReason = Some "terminated"
, locations = [ mercerIsland ]
, highlights =
[ "Built new base image for Deis components"
, "Research and development on a new builder component"
]
, hideFromResume = True
} }
, Job::{ , Job::{
, company = "Crowdflower (contract)" , company = Company::{
, name = "Appen"
, url = Some "https://appen.com/"
, tagline =
"is a company that uses crowdsourcing to have its customers submit tasks to be done, similar to Amazon's Mechanical Turk."
, location = mountainView // { city = "San Francisco", remote = True }
}
, title = "Consultant" , title = "Consultant"
, contract = True
, startDate = "2014-09-17" , startDate = "2014-09-17"
, endDate = Some "2014-10-15" , endDate = Some "2014-10-15"
, daysWorked = Some 28 , daysWorked = Some 28
, daysBetween = Some 21 , daysBetween = Some 21
, salary = hourly 90 , salary = hourly 90
, leaveReason = Some "contract not renewed" , leaveReason = Some "contract not renewed"
, locations = [ mercerIsland ]
, 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"
]
} }
, Job::{ , Job::{
, company = "VTCSecure (contract)" , company = Company::{
, name = "VTCSecure"
, url = Some "https://www.vtcsecure.com/"
, tagline =
"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."
, location = Location::{
, city = "Clearwater"
, stateOrProvince = "FL"
, country = "USA"
}
}
, title = "Consultant" , title = "Consultant"
, contract = True
, startDate = "2014-10-27" , startDate = "2014-10-27"
, endDate = Some "2015-02-09" , endDate = Some "2015-02-09"
, daysWorked = Some 105 , daysWorked = Some 105
, daysBetween = Some 12 , daysBetween = Some 12
, salary = hourly 90 , salary = hourly 90
, leaveReason = Some "contract not renewed" , leaveReason = Some "contract not renewed"
, locations = [ mercerIsland ]
, 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"
]
} }
, Job::{ , Job::{
, company = "IMVU" , company = imvu
, title = "Site Reliability Engineer" , title = "Site Reliability Engineer"
, startDate = "2015-03-30" , startDate = "2015-03-30"
, endDate = Some "2016-03-07" , endDate = Some "2016-03-07"
, daysWorked = Some 343 , daysWorked = Some 343
, daysBetween = Some 49 , daysBetween = Some 49
, salary = annual 125000 , salary = annual 125000 // { stock = Some Stock::{ amount = 20000 } }
, leaveReason = Some "demoted" , leaveReason = Some "demoted"
, locations = [ mountainView ]
, 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"
]
} }
, Job::{ , Job::{
, company = "IMVU" , company = imvu
, title = "Systems Administrator" , title = "Systems Administrator"
, startDate = "2016-03-08" , startDate = "2016-03-08"
, endDate = Some "2016-04-01" , endDate = Some "2016-04-01"
@ -66,39 +198,81 @@ in [ Job::{
, daysBetween = Some 1 , daysBetween = Some 1
, salary = annual 105000 , salary = annual 105000
, leaveReason = Some "quit" , leaveReason = Some "quit"
, locations = [ mountainView // { city = "Redwood City" } ]
} }
, Job::{ , Job::{
, company = "Pure Storage" , company = Company::{
, name = "Pure Storage"
, url = Some "https://www.purestorage.com/"
, tagline =
"a Mountain View, California-based enterprise data flash storage company founded in 2009. It is traded on the NYSE (PSTG)."
, location = mountainView
}
, title = "Member of Technical Staff" , title = "Member of Technical Staff"
, startDate = "2016-04-04" , startDate = "2016-04-04"
, endDate = Some "2016-08-03" , endDate = Some "2016-08-03"
, daysWorked = Some 121 , daysWorked = Some 121
, daysBetween = Some 3 , daysBetween = Some 3
, salary = annual 135000 , salary =
annual 135000
// { stock = Some Stock::{
, amount = 5000
, liquid = True
, kind = StockKind.Grant
}
}
, leaveReason = Some "quit" , leaveReason = Some "quit"
, locations = [ mountainView ]
, highlights = [ "Python 2 code maintenance", "Working with Foone" ]
} }
, Job::{ , Job::{
, company = "Backplane.io (defunct)" , company = Company::{
, name = "Backplane.io"
, defunct = True
, location = sf
}
, title = "Software Engineer" , title = "Software Engineer"
, startDate = "2016-08-24" , startDate = "2016-08-24"
, endDate = Some "2016-11-22" , endDate = Some "2016-11-22"
, daysWorked = Some 90 , daysWorked = Some 90
, daysBetween = Some 21 , daysBetween = Some 21
, salary = annual 105000 , salary = annual 105000 // { stock = Some Stock::{ amount = 85000 } }
, leaveReason = Some "terminated" , leaveReason = Some "terminated"
, locations = [ sf ]
, highlights =
[ "Performance monitoring of production servers"
, "Continuous deployment and development in Go"
, "Learning a lot about HTTP/2 and load balancing"
]
} }
, Job::{ , Job::{
, company = "Heroku (contract)" , company = Company::{
, name = "MBO Partners (Heroku)"
, tagline = "a staffing agency used to contract me for Heroku."
, location = Location::{
, city = "Herndon"
, stateOrProvince = "VA"
, country = "USA"
}
}
, title = "Consultant" , title = "Consultant"
, contract = True
, startDate = "2017-02-13" , startDate = "2017-02-13"
, endDate = Some "2017-11-13" , endDate = Some "2017-11-13"
, daysWorked = Some 273 , daysWorked = Some 273
, daysBetween = Some 83 , daysBetween = Some 83
, salary = hourly 120 , salary = hourly 120
, leaveReason = Some "hired" , leaveReason = Some "hired"
, locations = [ mountainView ]
} }
, Job::{ , Job::{
, company = "Heroku" , company = Company::{
, name = "Heroku"
, url = Some "https://heroku.com"
, tagline =
"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."
, location = sf
}
, title = "Senior Software Engineer" , title = "Senior Software Engineer"
, startDate = "2017-11-13" , startDate = "2017-11-13"
, endDate = Some "2019-03-08" , endDate = Some "2019-03-08"
@ -106,19 +280,41 @@ in [ Job::{
, daysBetween = Some 0 , daysBetween = Some 0
, salary = annual 150000 , salary = annual 150000
, leaveReason = Some "quit" , leaveReason = Some "quit"
, locations = [ mountainView, bellevue ]
, highlights =
[ "JVM Application Metrics"
, "Go Runtime Metrics Agent"
, "Other backend fixes and improvements on Threshold Autoscaling and Threshold Alerting"
, "Public-facing blogpost writing"
]
} }
, Job::{ , Job::{
, company = "Lightspeed POS" , company = Company::{
, name = "Lightspeed POS"
, url = Some "https://lightspeedhq.com"
, tagline =
"a provider of retail, ecommerce and point-of-sale solutions for small and medium scale businesses."
, location = montreal
}
, title = "Expert principal en fiabilité du site" , title = "Expert principal en fiabilité du site"
, startDate = "2019-05-06" , startDate = "2019-05-06"
, endDate = Some "2020-11-27" , endDate = Some "2020-11-27"
, daysWorked = Some 540 , daysWorked = Some 540
, daysBetween = Some 48 , daysBetween = Some 48
, salary = annualCAD 115000 , salary =
annualCAD 115000
// { stock = Some Stock::{ amount = 7500, liquid = True } }
, leaveReason = Some "quit" , leaveReason = Some "quit"
, locations = [ montreal ]
, highlights =
[ "Migration from cloud to cloud"
, "Work on the cloud platform initiative"
, "Crafting reliable infrastructure for clients of customers"
, "Creation of an internally consistent and extensible command line interface for internal tooling"
]
} }
, Job::{ , Job::{
, company = "Tailscale" , company = tailscale
, title = "Software Designer" , title = "Software Designer"
, startDate = "2020-12-14" , startDate = "2020-12-14"
, endDate = Some "2022-03-01" , endDate = Some "2022-03-01"
@ -126,11 +322,25 @@ in [ Job::{
, daysBetween = Some 0 , daysBetween = Some 0
, salary = annualCAD 135000 , salary = annualCAD 135000
, leaveReason = Some "raise" , leaveReason = Some "raise"
, locations = [ montreal // { remote = True }, ottawa ]
, highlights =
[ "Go programming"
, "SQL integrations"
, "Public-facing content writing"
, "Customer support"
]
} }
, Job::{ , Job::{
, company = "Tailscale" , company = tailscale
, title = "Archmage of Infrastructure" , title = "Archmage of Infrastructure"
, startDate = "2022-03-01" , startDate = "2022-03-01"
, salary = annualCAD 147150 , salary = annualCAD 147150
, locations = [ ottawa ]
, highlights =
[ "The first developer relations person at Tailscale"
, "Public-facing content writing"
, "Public speaking"
, "Developing custom integration solutions and supporting them"
]
} }
] ]

17
dhall/types/Company.dhall Normal file
View File

@ -0,0 +1,17 @@
let Location = ./Location.dhall
in { Type =
{ name : Text
, url : Optional Text
, tagline : Text
, location : Location.Type
, defunct : Bool
}
, default =
{ name = ""
, url = None Text
, tagline = ""
, location = Location::{=}
, defunct = False
}
}

33
dhall/types/Config.dhall Normal file
View File

@ -0,0 +1,33 @@
let Person = ./Person.dhall
let Author = ./Author.dhall
let Job = ./Job.dhall
let defaultPort = env:PORT ? 3030
let defaultWebMentionEndpoint =
env:WEBMENTION_ENDPOINT
? "https://mi.within.website/api/webmention/accept"
in { Type =
{ signalboost : List Person.Type
, authors : List Author.Type
, port : Natural
, clackSet : List Text
, resumeFname : Text
, webMentionEndpoint : Text
, miToken : Text
, jobHistory : List Job.Type
}
, default =
{ signalboost = [] : List Person.Type
, authors = [] : List Author.Type
, port = defaultPort
, clackSet = [ "Ashlynn" ]
, resumeFname = "./static/resume/resume.md"
, webMentionEndpoint = defaultWebMentionEndpoint
, miToken = "${env:MI_TOKEN as Text ? ""}"
, jobHistory = [] : List Job.Type
}
}

View File

@ -1,23 +1,35 @@
let Company = ./Company.dhall
let Salary = ./Salary.dhall let Salary = ./Salary.dhall
let Location = ./Location.dhall
in { Type = in { Type =
{ company : Text { company : Company.Type
, title : Text , title : Text
, contract : Bool
, startDate : Text , startDate : Text
, endDate : Optional Text , endDate : Optional Text
, daysWorked : Optional Natural , daysWorked : Optional Natural
, daysBetween : Optional Natural , daysBetween : Optional Natural
, salary : Salary.Type , salary : Salary.Type
, leaveReason : Optional Text , leaveReason : Optional Text
, locations : List Location.Type
, highlights : List Text
, hideFromResume : Bool
} }
, default = , default =
{ company = "Unknown" { company = Company::{=}
, title = "Unknown" , title = "Unknown"
, contract = False
, startDate = "0000-01-01" , startDate = "0000-01-01"
, endDate = None Text , endDate = None Text
, daysWorked = None Natural , daysWorked = None Natural
, daysBetween = None Natural , daysBetween = None Natural
, salary = Salary::{=} , salary = Salary::{=}
, leaveReason = None Text , leaveReason = None Text
, locations = [] : List Location.Type
, highlights = [] : List Text
, hideFromResume = False
} }
} }

View File

@ -0,0 +1,3 @@
{ Type = { city : Text, stateOrProvince : Text, country : Text, remote : Bool }
, default = { remote = True, city = "", stateOrProvince = "", country = "CAN" }
}

View File

@ -1,3 +1,11 @@
{ Type = { amount : Natural, currency : Text, per : Text } let Stock = ./Stock.dhall
, default = { amount = 0, currency = "USD", per = "year" }
in { Type =
{ amount : Natural
, currency : Text
, per : Text
, stock : Optional Stock.Type
}
, default =
{ amount = 0, currency = "USD", per = "year", stock = None Stock.Type }
} }

17
dhall/types/Stock.dhall Normal file
View File

@ -0,0 +1,17 @@
let StockKind = ./StockKind.dhall
in { Type =
{ kind : StockKind
, amount : Natural
, liquid : Bool
, vestingYears : Natural
, cliffYears : Natural
}
, default =
{ kind = StockKind.Options
, amount = 0
, liquid = False
, vestingYears = 4
, cliffYears = 1
}
}

View File

@ -0,0 +1 @@
< Grant | Options >

10
dhall/types/package.dhall Normal file
View File

@ -0,0 +1,10 @@
{ Author = ./Author.dhall
, Company = ./Company.dhall
, Config = ./Config.dhall
, Job = ./Job.dhall
, Location = ./Location.dhall
, Person = ./Person.dhall
, Salary = ./Salary.dhall
, Stock = ./Stock.dhall
, StockKind = ./StockKind.dhall
}

178
src/app/config.rs Normal file
View File

@ -0,0 +1,178 @@
use crate::signalboost::Person;
use maud::{html, Markup};
use serde::{Deserialize, Serialize};
use std::{
fmt::{self, Display},
path::PathBuf,
};
#[derive(Clone, Deserialize, Default)]
pub struct Config {
pub signalboost: Vec<Person>,
pub authors: Vec<Author>,
pub port: u16,
#[serde(rename = "clackSet")]
pub clack_set: Vec<String>,
#[serde(rename = "resumeFname")]
pub resume_fname: PathBuf,
#[serde(rename = "miToken")]
pub mi_token: String,
#[serde(rename = "jobHistory")]
pub job_history: Vec<Job>,
}
#[derive(Clone, Deserialize, Serialize)]
pub enum StockKind {
Grant,
Options,
}
impl Default for StockKind {
fn default() -> Self {
StockKind::Options
}
}
#[derive(Clone, Deserialize, Serialize, Default)]
pub struct Author {
pub name: String,
pub handle: String,
#[serde(rename = "picUrl")]
pub pic_url: Option<String>,
pub link: Option<String>,
pub twitter: Option<String>,
pub default: bool,
#[serde(rename = "inSystem")]
pub in_system: bool,
}
#[derive(Clone, Deserialize, Serialize, Default)]
pub struct Stock {
pub amount: i32,
#[serde(rename = "cliffYears")]
pub cliff_years: i32,
pub kind: StockKind,
pub liquid: bool,
#[serde(rename = "vestingYears")]
pub vesting_years: i32,
}
#[derive(Clone, Deserialize, Serialize, Default)]
pub struct Location {
pub city: String,
#[serde(rename = "stateOrProvince")]
pub state_or_province: String,
pub country: String,
pub remote: bool,
}
#[derive(Clone, Deserialize, Serialize, Default)]
pub struct Salary {
pub amount: i32,
pub per: String,
pub currency: String,
pub stock: Option<Stock>,
}
impl Display for Salary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}${}/{}", self.currency, self.amount, self.per)
}
}
impl Salary {
pub fn html(&self) -> Markup {
if self.stock.is_none() {
return html! { (self) };
}
let stock = self.stock.as_ref().unwrap();
html! {
details {
summary {
(self)
}
p{
(stock.amount)
" "
@if stock.liquid {
"liquid"
}
" "
@match stock.kind {
StockKind::Options => {
"options"
},
StockKind::Grant => {
"granted shares"
}
}
". Vesting for "
(stock.vesting_years)
" "
@if stock.vesting_years == 1 {
"year"
} @else {
"years"
}
" "
" with a cliff of "
(stock.cliff_years)
" "
@if stock.cliff_years == 1 {
"year"
} @else {
"years"
}
"."
}
}
}
}
}
#[derive(Clone, Deserialize, Serialize, Default)]
pub struct Job {
pub company: Company,
pub title: String,
#[serde(rename = "startDate")]
pub start_date: String,
#[serde(rename = "endDate")]
pub end_date: Option<String>,
#[serde(rename = "daysWorked")]
pub days_worked: Option<i32>,
#[serde(rename = "daysBetween")]
pub days_between: Option<i32>,
pub salary: Salary,
#[serde(rename = "leaveReason")]
pub leave_reason: Option<String>,
pub locations: Vec<Location>,
pub highlights: Vec<String>,
#[serde(rename = "hideFromResume")]
pub hide_from_resume: bool,
}
#[derive(Clone, Deserialize, Serialize, Default)]
pub struct Company {
pub name: String,
pub url: Option<String>,
pub tagline: String,
pub location: Location,
pub defunct: bool,
}
impl Job {
pub fn pay_history_row(&self) -> Markup {
html! {
tr {
td { (self.title) }
td { (self.start_date) }
td { (self.end_date.as_ref().unwrap_or(&"current".to_string())) }
td { (if self.days_worked.is_some() { self.days_worked.as_ref().unwrap().to_string() } else { "n/a".to_string() }) }
td { (self.salary.html()) }
td { (self.leave_reason.as_ref().unwrap_or(&"n/a".to_string())) }
}
}
}
}

View File

@ -1,74 +1,14 @@
use crate::{post::Post, signalboost::Person}; use crate::{post::Post, signalboost::Person};
use chrono::prelude::*; use chrono::prelude::*;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use maud::{html, Markup}; use std::{fs, path::PathBuf, sync::Arc};
use serde::{Deserialize, Serialize};
use std::{
fmt::{self, Display},
fs,
path::PathBuf,
sync::Arc,
};
use tracing::{error, instrument}; use tracing::{error, instrument};
pub mod config;
pub mod markdown; pub mod markdown;
pub mod poke; pub mod poke;
#[derive(Clone, Deserialize, Default)] pub use config::*;
pub struct Config {
pub(crate) signalboost: Vec<Person>,
#[serde(rename = "resumeFname")]
pub(crate) resume_fname: PathBuf,
#[serde(rename = "miToken")]
pub(crate) mi_token: String,
#[serde(rename = "jobHistory")]
pub(crate) job_history: Vec<Job>,
}
#[derive(Clone, Deserialize, Serialize, Default)]
pub struct Salary {
pub amount: i32,
pub per: String,
pub currency: String,
}
impl Display for Salary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}${}/{}", self.currency, self.amount, self.per)
}
}
#[derive(Clone, Deserialize, Serialize, Default)]
pub struct Job {
pub company: String,
pub title: String,
#[serde(rename = "startDate")]
pub start_date: String,
#[serde(rename = "endDate")]
pub end_date: Option<String>,
#[serde(rename = "daysWorked")]
pub days_worked: Option<i32>,
#[serde(rename = "daysBetween")]
pub days_between: Option<i32>,
pub salary: Salary,
#[serde(rename = "leaveReason")]
pub leave_reason: Option<String>,
}
impl Job {
pub fn pay_history_row(&self) -> Markup {
html! {
tr {
td { (self.title) }
td { (self.start_date) }
td { (self.end_date.as_ref().unwrap_or(&"current".to_string())) }
td { (if self.days_worked.is_some() { self.days_worked.as_ref().unwrap().to_string() } else { "n/a".to_string() }) }
td { (self.salary) }
td { (self.leave_reason.as_ref().unwrap_or(&"n/a".to_string())) }
}
}
}
}
#[instrument] #[instrument]
async fn patrons() -> Result<Option<patreon::Users>> { async fn patrons() -> Result<Option<patreon::Users>> {