diff options
| -rw-r--r-- | blog/my-career-in-dates-titles-salaries-2019-03-14.markdown | 22 | ||||
| -rw-r--r-- | blog/site-update-salary-transparency.markdown | 42 | ||||
| -rw-r--r-- | config.dhall | 74 | ||||
| -rw-r--r-- | default.nix | 1 | ||||
| -rw-r--r-- | dhall/authors.dhall | 31 | ||||
| -rw-r--r-- | dhall/jobHistory.dhall | 136 | ||||
| -rw-r--r-- | dhall/signalboost.dhall (renamed from signalboost.dhall) | 45 | ||||
| -rw-r--r-- | dhall/types/Author.dhall | 19 | ||||
| -rw-r--r-- | dhall/types/Job.dhall | 23 | ||||
| -rw-r--r-- | dhall/types/Person.dhall | 9 | ||||
| -rw-r--r-- | dhall/types/Salary.dhall | 3 | ||||
| -rw-r--r-- | src/app/markdown.rs | 9 | ||||
| -rw-r--r-- | src/app/mod.rs | 77 | ||||
| -rw-r--r-- | src/handlers/mod.rs | 28 | ||||
| -rw-r--r-- | src/main.rs | 6 | ||||
| -rw-r--r-- | src/post/mod.rs | 27 | ||||
| -rw-r--r-- | src/signalboost.rs | 3 | ||||
| -rw-r--r-- | src/tmpl/mod.rs | 20 | ||||
| -rw-r--r-- | templates/footer.rs.html | 1 | ||||
| -rw-r--r-- | templates/header.rs.html | 2 | ||||
| -rw-r--r-- | templates/salary_transparency.rs.html | 35 | ||||
| -rw-r--r-- | templates/signalboost.rs.html | 2 |
22 files changed, 481 insertions, 134 deletions
diff --git a/blog/my-career-in-dates-titles-salaries-2019-03-14.markdown b/blog/my-career-in-dates-titles-salaries-2019-03-14.markdown index b962ec9..97548ea 100644 --- a/blog/my-career-in-dates-titles-salaries-2019-03-14.markdown +++ b/blog/my-career-in-dates-titles-salaries-2019-03-14.markdown @@ -3,6 +3,12 @@ title: My Career So Far in Dates/Titles/Salaries date: 2019-03-14 --- +<div class="warning"><xeblog-conv name="Cadey" mood="coffee">This post is +outdated, see <a href="/salary-transparency">here</a> for more context on why +this data is made public. The table on this page will be automatically updated +to contain the data on my salary transparency page, but you should prefer that +page over this one when possible.</xeblog-conv></div> + Let this be inspiration to whoever is afraid of trying, failing and being fired. Every single one of these jobs has taught me lessons I've used daily in my career. @@ -26,21 +32,7 @@ might not want. The following table is a history of my software career by title, date and salary (company names are omitted). -| Title | Start Date | End Date | Days Worked | Days Between Jobs | Salary | How I Left | -|:----- |:---------- |:-------- |:----------- |:----------------- |:------ |:---------- | -| Junior Systems Administrator | November 11, 2013 | January 06, 2014 | 56 days | n/a | $50,000/year | Terminated | -| Software Engineering Intern | July 14, 2014 | August 27, 2014 | 44 days | 189 days | $35,000/year | Terminated | -| Consultant | September 17, 2014 | October 15, 2014 | 28 days | 21 days | $90/hour | Contract Lapsed | -| Consultant | October 27, 2014 | Feburary 9, 2015 | 105 days | 12 days | $90/hour | Contract Lapsed | -| Site Reliability Engineer | March 30, 2015 | March 7, 2016 | 343 days | 49 days | $125,000/year | Demoted | -| Systems Administrator | March 8, 2016 | April 1, 2016 | 24 days | 1 day | $105,000/year | Bad terms | -| Member of Technical Staff | April 4, 2016 | August 3, 2016 | 121 days | 3 days | $135,000/year | Bad terms | -| Software Engineer | August 24, 2016 | November 22, 2016 | 90 days | 21 days | $105,000/year | Terminated | -| Consultant | Feburary 13, 2017 | November 13, 2017 | 273 days | 83 days | don't remember | Hired | -| Senior Software Engineer | November 13, 2017 | March 8, 2019 | 480 days | 0 days | $150,000/year | Voulntary quit | -| Senior Site Reliability Expert | May 6, 2019 | October 27, 2020 | 540 days | 48 days | CAD$115,000/year (about USD$ 80k and change) | Voluntary quit | -| Software Designer | December 14, 2020 | *current* | n/a | n/a | CAD$135,000/year (about USD$ 105k and change) | raise | -| Archmage of Infrastructure | March 1, 2022 | *current* | n/a | n/a | CAD$147,150/year (about USD$ 115k and change) | n/a | +<xeblog-salary-history></xeblog-salary-history> Even though I've been fired three times, I don't regret my career as it's been thus far. I've been able to work on experimental technology integrating into diff --git a/blog/site-update-salary-transparency.markdown b/blog/site-update-salary-transparency.markdown new file mode 100644 index 0000000..c274cbc --- /dev/null +++ b/blog/site-update-salary-transparency.markdown @@ -0,0 +1,42 @@ +--- +title: "Site Update: Salary Transparency Page Added" +date: 2022-06-14 +author: Sephie +--- + +<xeblog-hero file="miku-dark-souls" prompt="hatsune miku, elden ring, dark souls, concept art, crowbar"></xeblog-hero> + +I have added a [salary transparency +page](https://xeiaso.net/salary-transparency) to the blog. This page lists my +salary for every job I've had in tech. I have had this data open to the public +for years, but I feel this should be more prominently displayed on my website. + +As someone who has seen pay discrimination work in action first-hand, data is +one of the ways that we can end this pointless hiding of information that leads +to people being uninformed and hirt by their lack of knowledge. By laying my +hand out in the open like this, I hope to ensure that people are better informed +about how much money they can make, so that they can be paid equally for equal +work. + +Raw, machine processable data (including employer names) is available at +`/api/salary_transparency.json`. The JSON format is not stable. Do not treat it as +such. I reserve the right to change the formatting or semantics of the JSON +format at any time without warning. The raw data is in `/dhall/jobHistory.dhall` +in my site's git repository. + +I have also taken the time to make sure that the [old +post](https://xeiaso.net/blog/my-career-in-dates-titles-salaries-2019-03-14) +maintains an up-to-date list. I do not want to break semantics on my website +without a very good reason. By leaving the old post un-updated, I feel it would +be doing a disservice to the community. + +Please consider publishing your salary data like this as well. By open, +voulntary transparency we can help to end stigmas around discussing pay and help +ensure that the next generations of people in tech are treated fairly. Stigmas +thrive in darkness but die in the light of day. You can help end the stigma by +playing your cards out in the open like this. + +It can be scary to do this; however every person that does it will make it that +much more easy for the next person to do it. + +Don't be afraid. diff --git a/config.dhall b/config.dhall index 4d86a88..8f4a9e4 100644 --- a/config.dhall +++ b/config.dhall @@ -1,38 +1,8 @@ -let Person = - { Type = - { name : Text - , tags : List Text - , gitLink : Optional Text - , twitter : Optional Text - } - , default = - { name = "" - , tags = [] : List Text - , gitLink = None Text - , twitter = None Text - } - } +let Person = ./dhall/types/Person.dhall -let Author = - { Type = - { name : Text - , handle : Text - , picUrl : Optional Text - , link : Optional Text - , twitter : Optional Text - , default : Bool - , inSystem : Bool - } - , default = - { name = "" - , handle = "" - , picUrl = None Text - , link = None Text - , twitter = None Text - , default = False - , inSystem = False - } - } +let Author = ./dhall/types/Author.dhall + +let Job = ./dhall/types/Job.dhall let defaultPort = env:PORT ? 3030 @@ -49,48 +19,24 @@ let Config = , resumeFname : Text , webMentionEndpoint : Text , miToken : Text + , jobHistory : List Job.Type } , default = { signalboost = [] : List Person.Type - , authors = - [ Author::{ - , name = "Xe Iaso" - , handle = "xe" - , picUrl = Some "/static/img/avatar.png" - , link = Some "https://christine.website" - , twitter = Some "theprincessxena" - , default = True - , inSystem = True - } - , Author::{ - , name = "Jessie" - , handle = "Heartmender" - , picUrl = Some - "https://cdn.christine.website/file/christine-static/img/UPRcp1pO_400x400.jpg" - , link = Some "https://heartmender.writeas.com" - , twitter = Some "BeJustFine" - , inSystem = True - } - , Author::{ - , name = "Ashe" - , handle = "ectamorphic" - , picUrl = Some - "https://cdn.christine.website/file/christine-static/img/FFVV1InX0AkDX3f_cropped_smol.jpg" - , inSystem = True - } - , Author::{ name = "Nicole", handle = "Twi", inSystem = True } - , Author::{ name = "Mai", handle = "Mai", inSystem = True } - ] + , 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::{ - , signalboost = ./signalboost.dhall + , signalboost = ./dhall/signalboost.dhall + , authors = ./dhall/authors.dhall , clackSet = [ "Ashlynn", "Terry Davis", "Dennis Ritchie", "Steven Hawking" ] + , jobHistory = ./dhall/jobHistory.dhall } diff --git a/default.nix b/default.nix index 0fa38c5..1f8021b 100644 --- a/default.nix +++ b/default.nix @@ -48,7 +48,6 @@ in pkgs.stdenv.mkDerivation { cp -rf $src/blog $out/blog cp -rf $src/css $out/css cp -rf $src/gallery $out/gallery - cp -rf $src/signalboost.dhall $out/signalboost.dhall cp -rf $src/static $out/static cp -rf $src/talks $out/talks diff --git a/dhall/authors.dhall b/dhall/authors.dhall new file mode 100644 index 0000000..38c459e --- /dev/null +++ b/dhall/authors.dhall @@ -0,0 +1,31 @@ +let Author = ./types/Author.dhall + +in [ Author::{ + , name = "Xe Iaso" + , handle = "xe" + , picUrl = Some "/static/img/avatar.png" + , link = Some "https://christine.website" + , twitter = Some "theprincessxena" + , default = True + , inSystem = True + } + , Author::{ + , name = "Jessie" + , handle = "Heartmender" + , picUrl = Some + "https://cdn.christine.website/file/christine-static/img/UPRcp1pO_400x400.jpg" + , link = Some "https://heartmender.writeas.com" + , twitter = Some "BeJustFine" + , inSystem = True + } + , Author::{ + , name = "Ashe" + , handle = "ectamorphic" + , picUrl = Some + "https://cdn.christine.website/file/christine-static/img/FFVV1InX0AkDX3f_cropped_smol.jpg" + , inSystem = True + } + , Author::{ name = "Nicole", handle = "Twi", inSystem = True } + , Author::{ name = "Mai", handle = "Mai", inSystem = True } + , Author::{ name = "Sephira", handle = "Sephie", inSystem = True } + ] diff --git a/dhall/jobHistory.dhall b/dhall/jobHistory.dhall new file mode 100644 index 0000000..1148587 --- /dev/null +++ b/dhall/jobHistory.dhall @@ -0,0 +1,136 @@ +let Job = ./types/Job.dhall + +let Salary = ./types/Salary.dhall + +let annual = \(rate : Natural) -> Salary::{ amount = rate } + +let hourly = \(rate : Natural) -> Salary::{ amount = rate, per = "hour" } + +let annualCAD = \(rate : Natural) -> Salary::{ amount = rate, currency = "CAD" } + +in [ Job::{ + , company = "Symplicity" + , title = "Junior Systems Administrator" + , startDate = "2013-11-11" + , endDate = Some "2014-01-06" + , daysWorked = Some 56 + , salary = annual 50000 + , leaveReason = Some "terminated" + } + , Job::{ + , company = "OpDemand" + , title = "Software Engineering Intern" + , startDate = "2014-07-14" + , endDate = Some "2014-08-27" + , daysWorked = Some 44 + , daysBetween = Some 189 + , salary = annual 35000 + , leaveReason = Some "terminated" + } + , Job::{ + , company = "Crowdflower (contract)" + , title = "Consultant" + , startDate = "2014-09-17" + , endDate = Some "2014-10-15" + , daysWorked = Some 28 + , daysBetween = Some 21 + , salary = hourly 90 + , leaveReason = Some "contract not renewed" + } + , Job::{ + , company = "VTCSecure (contract)" + , title = "Consultant" + , startDate = "2014-10-27" + , endDate = Some "2015-02-09" + , daysWorked = Some 105 + , daysBetween = Some 12 + , salary = hourly 90 + , leaveReason = Some "contract not renewed" + } + , Job::{ + , company = "IMVU" + , title = "Site Reliability Engineer" + , startDate = "2015-03-30" + , endDate = Some "2016-03-07" + , daysWorked = Some 343 + , daysBetween = Some 49 + , salary = annual 125000 + , leaveReason = Some "demoted" + } + , Job::{ + , company = "IMVU" + , title = "Systems Administrator" + , startDate = "2016-03-08" + , endDate = Some "2016-04-01" + , daysWorked = Some 24 + , daysBetween = Some 1 + , salary = annual 105000 + , leaveReason = Some "quit" + } + , Job::{ + , company = "Pure Storage" + , title = "Member of Technical Staff" + , startDate = "2016-04-04" + , endDate = Some "2016-08-03" + , daysWorked = Some 121 + , daysBetween = Some 3 + , salary = annual 135000 + , leaveReason = Some "quit" + } + , Job::{ + , company = "Backplane.io (defunct)" + , title = "Software Engineer" + , startDate = "2016-08-24" + , endDate = Some "2016-11-22" + , daysWorked = Some 90 + , daysBetween = Some 21 + , salary = annual 105000 + , leaveReason = Some "terminated" + } + , Job::{ + , company = "Heroku (contract)" + , title = "Consultant" + , startDate = "2017-02-13" + , endDate = Some "2017-11-13" + , daysWorked = Some 273 + , daysBetween = Some 83 + , salary = hourly 120 + , leaveReason = Some "hired" + } + , Job::{ + , company = "Heroku" + , title = "Senior Software Engineer" + , startDate = "2017-11-13" + , endDate = Some "2019-03-08" + , daysWorked = Some 480 + , daysBetween = Some 0 + , salary = annual 150000 + , leaveReason = Some "quit" + } + , Job::{ + , company = "Lightspeed POS" + , title = "Expert principal en fiabilité du site" + , startDate = "2019-05-06" + , endDate = Some "2020-11-27" + , daysWorked = Some 540 + , daysBetween = Some 48 + , salary = annualCAD 115000 + , leaveReason = Some "quit" + } + , Job::{ + , company = "Tailscale" + , title = "Software Designer" + , startDate = "2020-12-14" + , endDate = Some "2022-03-01" + , daysWorked = Some 442 + , daysBetween = Some 0 + , salary = annualCAD 135000 + , leaveReason = Some "raise" + } + , Job::{ + , company = "Tailscale" + , title = "Archmage of Infrastructure" + , startDate = "2022-03-01" + , salary = annualCAD 147150 + } + ] diff --git a/signalboost.dhall b/dhall/signalboost.dhall index 6ec366f..39f41f3 100644 --- a/signalboost.dhall +++ b/dhall/signalboost.dhall @@ -1,17 +1,4 @@ -let Person = - { Type = - { name : Text - , tags : List Text - , gitLink : Optional Text - , twitter : Optional Text - } - , default = - { name = "" - , tags = [] : List Text - , gitLink = None Text - , twitter = None Text - } - } +let Person = ./types/Person.dhall in [ Person::{ , name = "Christian Sullivan" @@ -278,20 +265,20 @@ in [ Person::{ , gitLink = Some "https://github.com/henri" , twitter = Some "https://twitter.com/henri_shustak" } - , Person::{ - , name = "Gabriel Simmer" - , tags = - [ "golang" - , "backend" - , "javascript" - , "python" - , "software" - , "full-stack" - , "linux" - , "devops" - , "developer tooling" - ] - , gitLink = Some "https://github.com/gmemstr" - , twitter = Some "https://twitter.com/gmem_" + , Person::{ + , name = "Gabriel Simmer" + , tags = + [ "golang" + , "backend" + , "javascript" + , "python" + , "software" + , "full-stack" + , "linux" + , "devops" + , "developer tooling" + ] + , gitLink = Some "https://github.com/gmemstr" + , twitter = Some "https://twitter.com/gmem_" } ] diff --git a/dhall/types/Author.dhall b/dhall/types/Author.dhall new file mode 100644 index 0000000..dc0b1c3 --- /dev/null +++ b/dhall/types/Author.dhall @@ -0,0 +1,19 @@ +{ Type = + { name : Text + , handle : Text + , picUrl : Optional Text + , link : Optional Text + , twitter : Optional Text + , default : Bool + , inSystem : Bool + } +, default = + { name = "" + , handle = "" + , picUrl = None Text + , link = None Text + , twitter = None Text + , default = False + , inSystem = False + } +} diff --git a/dhall/types/Job.dhall b/dhall/types/Job.dhall new file mode 100644 index 0000000..a079d3c --- /dev/null +++ b/dhall/types/Job.dhall @@ -0,0 +1,23 @@ +let Salary = ./Salary.dhall + +in { Type = + { company : Text + , title : Text + , startDate : Text + , endDate : Optional Text + , daysWorked : Optional Natural + , daysBetween : Optional Natural + , salary : Salary.Type + , leaveReason : Optional Text + } + , default = + { company = "Unknown" + , title = "Unknown" + , startDate = "0000-01-01" + , endDate = None Text + , daysWorked = None Natural + , daysBetween = None Natural + , salary = Salary::{=} + , leaveReason = None Text + } + } diff --git a/dhall/types/Person.dhall b/dhall/types/Person.dhall new file mode 100644 index 0000000..b1628eb --- /dev/null +++ b/dhall/types/Person.dhall @@ -0,0 +1,9 @@ +{ Type = + { name : Text + , tags : List Text + , gitLink : Optional Text + , twitter : Optional Text + } +, default = + { name = "", tags = [] : List Text, gitLink = None Text, twitter = None Text } +} diff --git a/dhall/types/Salary.dhall b/dhall/types/Salary.dhall new file mode 100644 index 0000000..37dfce7 --- /dev/null +++ b/dhall/types/Salary.dhall @@ -0,0 +1,3 @@ +{ Type = { amount : Natural, currency : Text, per : Text } +, default = { amount = 0, currency = "USD", per = "year" } +} diff --git a/src/app/markdown.rs b/src/app/markdown.rs index f6ae342..d73a5c5 100644 --- a/src/app/markdown.rs +++ b/src/app/markdown.rs @@ -1,3 +1,4 @@ +use crate::app::Config; use crate::templates::Html; use color_eyre::eyre::{Result, WrapErr}; use comrak::nodes::{Ast, AstNode, NodeValue}; @@ -9,13 +10,14 @@ use comrak::{ use lazy_static::lazy_static; use lol_html::{element, html_content::ContentType, rewrite_str, RewriteStrSettings}; use std::cell::RefCell; +use std::sync::Arc; use url::Url; lazy_static! { static ref SYNTECT_ADAPTER: SyntectAdapter<'static> = SyntectAdapter::new("base16-mocha.dark"); } -pub fn render(inp: &str) -> Result<String> { +pub fn render(cfg: Arc<Config>, inp: &str) -> Result<String> { let mut options = ComrakOptions::default(); options.extension.autolink = true; @@ -100,6 +102,11 @@ pub fn render(inp: &str) -> Result<String> { let file = el.get_attribute("file").expect("wanted xeblog-hero to contain file"); el.replace(&crate::tmpl::xeblog_hero(file, el.get_attribute("prompt")).0, ContentType::Html); Ok(()) + }), + element!("xeblog-salary-history", |el| { + el.replace(&crate::tmpl::xeblog_salary_history(cfg.clone()).0, ContentType::Html); + + Ok(()) }) ], ..RewriteStrSettings::default() diff --git a/src/app/mod.rs b/src/app/mod.rs index 7125cf8..a12d1c6 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,23 +1,73 @@ use crate::{post::Post, signalboost::Person}; -use color_eyre::eyre::Result; use chrono::prelude::*; -use serde::Deserialize; +use color_eyre::eyre::Result; +use maud::{html, Markup}; +use serde::{Deserialize, Serialize}; use std::{ + fmt::{self, Display}, fs, path::PathBuf, + sync::Arc, }; use tracing::{error, instrument}; pub mod markdown; pub mod poke; -#[derive(Clone, Deserialize)] +#[derive(Clone, Deserialize, Default)] 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] @@ -57,7 +107,7 @@ async fn patrons() -> Result<Option<patreon::Users>> { pub const ICON: &'static str = "https://xeiaso.net/static/img/avatar.png"; pub struct State { - pub cfg: Config, + pub cfg: Arc<Config>, pub signalboost: Vec<Person>, pub resume: String, pub blog: Vec<Post>, @@ -71,14 +121,17 @@ pub struct State { } pub async fn init(cfg: PathBuf) -> Result<State> { - let cfg: Config = serde_dhall::from_file(cfg).parse()?; + let cfg: Arc<Config> = Arc::new(serde_dhall::from_file(cfg).parse()?); let sb = cfg.signalboost.clone(); - let resume = fs::read_to_string(cfg.resume_fname.clone())?; - let resume: String = markdown::render(&resume)?; - let mi = mi::Client::new(cfg.mi_token.clone(), crate::APPLICATION_NAME.to_string())?; - let blog = crate::post::load("blog").await?; - let gallery = crate::post::load("gallery").await?; - let talks = crate::post::load("talks").await?; + let resume = fs::read_to_string(cfg.clone().resume_fname.clone())?; + let resume: String = markdown::render(cfg.clone(), &resume)?; + let mi = mi::Client::new( + cfg.clone().mi_token.clone(), + crate::APPLICATION_NAME.to_string(), + )?; + let blog = crate::post::load(cfg.clone(), "blog").await?; + let gallery = crate::post::load(cfg.clone(), "gallery").await?; + let talks = crate::post::load(cfg.clone(), "talks").await?; let mut everything: Vec<Post> = vec![]; { @@ -99,7 +152,7 @@ pub async fn init(cfg: PathBuf) -> Result<State> { .filter(|p| today.num_days_from_ce() >= p.date.num_days_from_ce()) .take(5) .collect(); - + let mut jfb = jsonfeed::Feed::builder() .title("Xe's Blog") .description("My blog posts and rants about various technology things.") diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index fa8203c..fc2a154 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -1,9 +1,13 @@ -use crate::{app::State, templates}; +use crate::{ + app::{Job, State}, + templates, +}; use axum::{ body, extract::Extension, http::StatusCode, response::{Html, IntoResponse, Response}, + Json, }; use chrono::{Datelike, Timelike, Utc, Weekday}; use lazy_static::lazy_static; @@ -74,6 +78,28 @@ pub async fn feeds() -> Result { #[axum_macros::debug_handler] #[instrument(skip(state))] +pub async fn salary_transparency(Extension(state): Extension<Arc<State>>) -> Result { + HIT_COUNTER + .with_label_values(&["salary_transparency"]) + .inc(); + let state = state.clone(); + let mut result: Vec<u8> = vec![]; + templates::salary_transparency(&mut result, state.cfg.clone())?; + Ok(Html(result)) +} + +#[axum_macros::debug_handler] +#[instrument(skip(state))] +pub async fn salary_transparency_json(Extension(state): Extension<Arc<State>>) -> Json<Vec<Job>> { + HIT_COUNTER + .with_label_values(&["salary_transparency_json"]) + .inc(); + + Json(state.clone().cfg.clone().job_history.clone()) +} + +#[axum_macros::debug_handler] +#[instrument(skip(state))] pub async fn resume(Extension(state): Extension<Arc<State>>) -> Result { HIT_COUNTER.with_label_values(&["resume"]).inc(); let state = state.clone(); diff --git a/src/main.rs b/src/main.rs index 7611176..eb4b9e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -155,6 +155,11 @@ async fn main() -> Result<()> { }, ), ) + // api + .route( + "/api/salary_transparency.json", + get(handlers::salary_transparency_json), + ) // static pages .route("/", get(handlers::index)) .route("/contact", get(handlers::contact)) @@ -162,6 +167,7 @@ async fn main() -> Result<()> { .route("/resume", get(handlers::resume)) .route("/patrons", get(handlers::patrons)) .route("/signalboost", get(handlers::signalboost)) + .route("/salary-transparency", get(handlers::salary_transparency)) // feeds .route("/blog.json", get(handlers::feeds::jsonfeed)) .route("/blog.atom", get(handlers::feeds::atom)) diff --git a/src/post/mod.rs b/src/post/mod.rs index 3e4cb8a..96c3e73 100644 --- a/src/post/mod.rs +++ b/src/post/mod.rs @@ -1,8 +1,9 @@ +use crate::app::Config; use chrono::prelude::*; use color_eyre::eyre::{eyre, Result, WrapErr}; use glob::glob; use serde::{Deserialize, Serialize}; -use std::{borrow::Borrow, cmp::Ordering, path::PathBuf}; +use std::{borrow::Borrow, cmp::Ordering, path::PathBuf, sync::Arc}; use tokio::fs; pub mod frontmatter; @@ -81,7 +82,12 @@ impl Post { } } -async fn read_post(dir: &str, fname: PathBuf, cli: &Option<mi::Client>) -> Result<Post> { +async fn read_post( + cfg: Arc<Config>, + dir: &str, + fname: PathBuf, + cli: &Option<mi::Client>, +) -> Result<Post> { debug!( "loading {}", fname.clone().into_os_string().into_string().unwrap() @@ -96,7 +102,7 @@ async fn read_post(dir: &str, fname: PathBuf, cli: &Option<mi::Client>) -> Resul let date = NaiveDate::parse_from_str(&front_matter.clone().date, "%Y-%m-%d") .map_err(|why| eyre!("error parsing date in {:?}: {}", fname, why))?; let link = format!("{}/{}", dir, fname.file_stem().unwrap().to_str().unwrap()); - let body_html = crate::app::markdown::render(&body) + let body_html = crate::app::markdown::render(cfg.clone(), &body) .wrap_err_with(|| format!("can't parse markdown for {:?}", fname))?; let date: DateTime<FixedOffset> = DateTime::<Utc>::from_utc(NaiveDateTime::new(date, NaiveTime::from_hms(0, 0, 0)), Utc) @@ -144,7 +150,7 @@ async fn read_post(dir: &str, fname: PathBuf, cli: &Option<mi::Client>) -> Resul }) } -pub async fn load(dir: &str) -> Result<Vec<Post>> { +pub async fn load(cfg: Arc<Config>, dir: &str) -> Result<Vec<Post>> { let cli = match std::env::var("MI_TOKEN") { Ok(token) => mi::Client::new(token.to_string(), crate::APPLICATION_NAME.to_string()).ok(), Err(_) => None, @@ -152,7 +158,7 @@ pub async fn load(dir: &str) -> Result<Vec<Post>> { let futs = glob(&format!("{}/*.markdown", dir))? .filter_map(Result::ok) - .map(|fname| read_post(dir, fname, cli.borrow())); + .map(|fname| read_post(cfg.clone(), dir, fname, cli.borrow())); let mut result: Vec<Post> = futures::future::join_all(futs) .await @@ -172,25 +178,30 @@ pub async fn load(dir: &str) -> Result<Vec<Post>> { #[cfg(test)] mod tests { use super::*; + use crate::app::Config; use color_eyre::eyre::Result; + use std::sync::Arc; #[tokio::test] async fn blog() { let _ = pretty_env_logger::try_init(); - load("blog").await.expect("posts to load"); + let cfg = Arc::new(Config::default()); + load(cfg, "blog").await.expect("posts to load"); } #[tokio::test] async fn gallery() -> Result<()> { let _ = pretty_env_logger::try_init(); - load("gallery").await?; + let cfg = Arc::new(Config::default()); + load(cfg, "gallery").await?; Ok(()) } #[tokio::test] async fn talks() -> Result<()> { let _ = pretty_env_logger::try_init(); - load("talks").await?; + let cfg = Arc::new(Config::default()); + load(cfg, "talks").await?; Ok(()) } } diff --git a/src/signalboost.rs b/src/signalboost.rs index a57a976..3d1b534 100644 --- a/src/signalboost.rs +++ b/src/signalboost.rs @@ -16,7 +16,8 @@ mod tests { use color_eyre::eyre::Result; #[test] fn load() -> Result<()> { - let _people: Vec<super::Person> = serde_dhall::from_file("./signalboost.dhall").parse()?; + let _people: Vec<super::Person> = + serde_dhall::from_file("./dhall/signalboost.dhall"). |
