aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXe Iaso <me@christine.website>2022-06-14 15:04:17 -0400
committerGitHub <noreply@github.com>2022-06-14 15:04:17 -0400
commitad6fba4c79e8b5ab08e2f0db8bc4087f03151f7f (patch)
tree9888fe24eb3ea35ea0f9b54af8723b4a000e6ad9
parent7541df778165b5a96da714256d011685b476abc0 (diff)
downloadxesite-ad6fba4c79e8b5ab08e2f0db8bc4087f03151f7f.tar.xz
xesite-ad6fba4c79e8b5ab08e2f0db8bc4087f03151f7f.zip
Add salary transparency page (#492)
* Move dhall data and types into `/dhall` folder * Reformat salary transparency data into Dhall * Wire up the old salary transparency page with a custom element * Wire up a new salary transparency page * Expose raw data as JSON * Make dhall types more portable * Remove gallery from the navbar * Make signal boost page point to the new data location * Add salary transparency page to the footer of the site * Add site update post for this Signed-off-by: Xe <me@xeiaso.net>
-rw-r--r--blog/my-career-in-dates-titles-salaries-2019-03-14.markdown22
-rw-r--r--blog/site-update-salary-transparency.markdown42
-rw-r--r--config.dhall74
-rw-r--r--default.nix1
-rw-r--r--dhall/authors.dhall31
-rw-r--r--dhall/jobHistory.dhall136
-rw-r--r--dhall/signalboost.dhall (renamed from signalboost.dhall)45
-rw-r--r--dhall/types/Author.dhall19
-rw-r--r--dhall/types/Job.dhall23
-rw-r--r--dhall/types/Person.dhall9
-rw-r--r--dhall/types/Salary.dhall3
-rw-r--r--src/app/markdown.rs9
-rw-r--r--src/app/mod.rs77
-rw-r--r--src/handlers/mod.rs28
-rw-r--r--src/main.rs6
-rw-r--r--src/post/mod.rs27
-rw-r--r--src/signalboost.rs3
-rw-r--r--src/tmpl/mod.rs20
-rw-r--r--templates/footer.rs.html1
-rw-r--r--templates/header.rs.html2
-rw-r--r--templates/salary_transparency.rs.html35
-rw-r--r--templates/signalboost.rs.html2
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")