fuck it, we resume
This commit is contained in:
parent
6f00e10641
commit
bfae46c42f
8 changed files with 303 additions and 3 deletions
|
@ -77,7 +77,7 @@ a {
|
|||
text-decoration: underline solid currentColor 1px;
|
||||
}
|
||||
|
||||
code {
|
||||
code, kbd {
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
@ -109,7 +109,7 @@ select {
|
|||
outline: 1ch solid var(--color-text);
|
||||
}
|
||||
|
||||
input[type=text], input[type=url], textarea, select {
|
||||
input[type=text], input[type=url], input[type=password], textarea, select {
|
||||
padding-inline: 1ch;
|
||||
border-inline: 1ch solid var(--color-accent);
|
||||
background-color: var(--color-bg-scrim);
|
||||
|
|
BIN
assets/fonts/Lexend/Lexend.ttf
Normal file
BIN
assets/fonts/Lexend/Lexend.ttf
Normal file
Binary file not shown.
15
assets/print.css
Normal file
15
assets/print.css
Normal file
|
@ -0,0 +1,15 @@
|
|||
footer,
|
||||
.matkap_ascii,
|
||||
.noprint {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Lexend";
|
||||
font-weight: regular;
|
||||
src: url("./Lexend/Lexend.ttf");
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: "Lexend";
|
||||
}
|
93
index.js
93
index.js
|
@ -8,6 +8,7 @@ import { JSDOM } from "jsdom";
|
|||
import Parser from "rss-parser";
|
||||
import nacl from "tweetnacl";
|
||||
import expressBasicAuth from "express-basic-auth";
|
||||
import crypto from "node:crypto";
|
||||
import { getRelativeTime } from "./modules/relativeTime.js";
|
||||
import { fromHex } from "./modules/fromHex.js";
|
||||
|
||||
|
@ -487,6 +488,98 @@ app.get('/updates', async (req, res) => {
|
|||
});
|
||||
});
|
||||
|
||||
const enc_key = Buffer.from(process.env.RESUME_ENCRYPTION_KEY, "hex");
|
||||
|
||||
app.get('/resumeEnc', async (req, res) => {
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv(
|
||||
"aes-256-cbc", enc_key, iv
|
||||
);
|
||||
|
||||
let encrypted = cipher.update(req.query.text, 'utf8', 'hex');
|
||||
encrypted += cipher.final('hex');
|
||||
|
||||
res.send(encrypted + "\n" + iv.toString('hex'));
|
||||
});
|
||||
|
||||
function decipher(file, key) {
|
||||
let hexKey;
|
||||
try {
|
||||
hexKey = Buffer.from(key, "hex");
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
const [enc, iv] = file.split("\n");
|
||||
|
||||
let decipher;
|
||||
try {
|
||||
decipher = crypto.createDecipheriv(
|
||||
"aes-256-cbc", hexKey, Buffer.from(iv, "hex")
|
||||
);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
let decrypted = decipher.update(enc, 'hex', 'utf8');
|
||||
decrypted += decipher.final('utf8');
|
||||
|
||||
let decryptedJSON;
|
||||
try {
|
||||
decryptedJSON = JSON.parse(decrypted);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
}
|
||||
return decryptedJSON;
|
||||
}
|
||||
|
||||
app.get('/resume', async (req, res) => {
|
||||
if (req.query.key == null || req.query.key.length < 1) return res.render('resumeEntry', { badError: false });
|
||||
const encryptedPD = await fetch(process.env.RESUME_DATA)
|
||||
.catch(() => res.status(500).send())
|
||||
.then((res) => res.text());
|
||||
const encryptedJobs = await fetch(process.env.RESUME_JOBS)
|
||||
.catch(() => res.status(500).send())
|
||||
.then((res) => res.text());
|
||||
const encryptedEducation = await fetch(process.env.RESUME_EDU)
|
||||
.catch(() => res.status(500).send())
|
||||
.then((res) => res.text());
|
||||
const unencryptedOrgs = await fetch(process.env.RESUME_ORGS)
|
||||
.catch(() => res.status(500).send())
|
||||
.then((res) => res.json());
|
||||
|
||||
let unencryptedPD;
|
||||
try {
|
||||
unencryptedPD = decipher(encryptedPD, req.query.key);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return res.render('resumeEntry', { badError: true });
|
||||
}
|
||||
let unencryptedJobs;
|
||||
try {
|
||||
unencryptedJobs = decipher(encryptedJobs, req.query.key);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return res.render('resumeEntry', { badError: true });
|
||||
}
|
||||
let unencryptedEducation;
|
||||
try {
|
||||
unencryptedEducation = decipher(encryptedEducation, req.query.key);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return res.render('resumeEntry', { badError: true });
|
||||
}
|
||||
|
||||
console.log(unencryptedOrgs)
|
||||
|
||||
res.render('resume', {
|
||||
pd: unencryptedPD,
|
||||
jobs: unencryptedJobs,
|
||||
education: unencryptedEducation,
|
||||
orgs: unencryptedOrgs
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/sites', async (req, res) => {
|
||||
const buttonsJson = await fetch("https://cdn.abtmtr.link/site_content/buttons.json")
|
||||
.catch(() => res.status(500).send())
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
"link": "sites",
|
||||
"description": "a collection of other sites in the form of 88x31s."
|
||||
},
|
||||
{
|
||||
"link": "resume",
|
||||
"description": "my job history, in general."
|
||||
},
|
||||
{
|
||||
"link": "about",
|
||||
"description": "who runs abtmtr.link?"
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
<title>abtmtr.link</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="abtmtr-mininq-key" content="cf28339198953cab0c635c03030fa23d63e88db92126fab5d358b7cb1aded015">
|
||||
<link rel="stylesheet" href="/assets/style.css" />
|
||||
<link rel="stylesheet" media="screen" href="/assets/style.css" />
|
||||
<link rel="stylesheet" media="print" href="/assets/print.css" />
|
||||
<!-- <link rel="stylesheet" href="/assets/print.css" /> -->
|
||||
<link rel="me" href="https://abtmtr.link/about">
|
||||
<link rel="me" href="https://abtmtr.link/blurbs/#Hx7CuB4_zIWMAsOZlyyqsUm2upXEEYEl">
|
||||
<link rel="me" href="https://abtmtr.link/blurbs/#aQArHxCaViG-Qw0aFkBRhQWbRsoxUqsB">
|
||||
|
|
159
views/pages/resume.ejs
Normal file
159
views/pages/resume.ejs
Normal file
|
@ -0,0 +1,159 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<%- include("../components/page-head.ejs") %>
|
||||
<body>
|
||||
<style>
|
||||
@media print {
|
||||
:root {
|
||||
--color: #FF4000;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
opacity: 0.8;
|
||||
font-weight: normal;
|
||||
padding-left: 0.5rem;
|
||||
border-left: 4px solid var(--color);
|
||||
}
|
||||
|
||||
a {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
p.address {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
p.pd {
|
||||
margin-top: -0.5em;
|
||||
}
|
||||
|
||||
p.address,
|
||||
p.pd {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<main>
|
||||
<h1><%= pd.name[0] %> <%= pd.name[1][0] %>. <%= pd.name[2] %></h1>
|
||||
<p class="pd">
|
||||
<a href="tel:<%= pd.phone.region %><%= pd.phone.area %>-<%= pd.phone.local %>">
|
||||
<%= pd.phone.region %> (<%= pd.phone.area %>) <%= pd.phone.local %></a>
|
||||
/
|
||||
<a href="mailto:<%= pd.email %>">
|
||||
<%= pd.email %>
|
||||
</a>
|
||||
</p>
|
||||
<p class="address">
|
||||
<%= pd.address.street_address %><br />
|
||||
<%= pd.address.city %>,
|
||||
<%= pd.address.state %>
|
||||
<%= pd.address.postal %>
|
||||
</p>
|
||||
<p class="noprint">
|
||||
<span style="opacity: 0.5;">
|
||||
Hint: Use <kbd>CTRL</kbd> <kbd>P</kbd> to print this page and get a more readable layout.
|
||||
</span>
|
||||
</p>
|
||||
<h2>Organizations / Volunteering</h2>
|
||||
<ul>
|
||||
<% orgs.forEach((org) => { %>
|
||||
<li>
|
||||
<h3><a href="<%= org.url %>" target="_blank"><%= org.name %></a></h3>
|
||||
<p>
|
||||
<% if (org.main && org.main.url) { %>
|
||||
<a href="<%= org.main.url %>" target="_blank"><%= org.main.title %></a>
|
||||
<% } else if (org.main) { %>
|
||||
<%= org.main.title %>
|
||||
<% } %>
|
||||
<% if (org.main && org.main.email) { %>
|
||||
/ <span>
|
||||
<a href="mailto:<%= org.main.email %>">
|
||||
<%= org.main.email %>
|
||||
</a>
|
||||
</span>
|
||||
<% } %>
|
||||
</p>
|
||||
<ul>
|
||||
<% org.involvement.forEach((period) => { %>
|
||||
<li>
|
||||
<h4><%= period.description %></h4>
|
||||
<p>
|
||||
<% if (period.supervisor) { %>
|
||||
<span style="opacity: 0.5;">Supervised by <%= period.supervisor %></span>
|
||||
<% } %>
|
||||
</p>
|
||||
<p>
|
||||
<% if (period.date) { %>
|
||||
<span>Done <%= period.date %></span>
|
||||
<% } else if (period.start) { %>
|
||||
<%= period.start %> - <%= period.end || "Present" %>
|
||||
<% } %>
|
||||
</p>
|
||||
</li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
</li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
<h2>Education</h2>
|
||||
<ul>
|
||||
<% education.forEach((school) => { %>
|
||||
<li>
|
||||
<h3><a href="<%= school.url %>" target="_blank"><%= school.name %></a></h3>
|
||||
<p>
|
||||
<span style="opacity: 0.5;" class="address">
|
||||
<%= school.address.street_address %>,
|
||||
<%= school.address.city %>,
|
||||
<%= school.address.state %>
|
||||
<%= school.address.postal %>
|
||||
</span>
|
||||
</p>
|
||||
<p>
|
||||
<%= school.start || "???" %> - <%= school.end || "Present" %><br />
|
||||
<span style="opacity: 0.5;">Learned <%= school.experience.join(", ") %></span>
|
||||
</p>
|
||||
</li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
<h2>Experience</h2>
|
||||
<ul>
|
||||
<% jobs.forEach((job) => { %>
|
||||
<li>
|
||||
<h3><a href="<%= job.url %>" target="_blank"><%= job.name %></a></h3>
|
||||
<p>
|
||||
<span style="opacity: 0.5;" class="address">
|
||||
<%= job.address.street_address %>,
|
||||
<%= job.address.city %>,
|
||||
<%= job.address.state %>
|
||||
<%= job.address.postal %>
|
||||
</span>
|
||||
</p>
|
||||
<ul>
|
||||
<% job.involvement.forEach((period) => { %>
|
||||
<li>
|
||||
<h4><%= period.description %></h4>
|
||||
<p>
|
||||
<span style="opacity: 0.5;">Supervised by <%= period.supervisor %></span>
|
||||
</p>
|
||||
<p>
|
||||
<%= period.start || "???" %> - <%= period.end || "Present" %>
|
||||
</p>
|
||||
</li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
<p>
|
||||
<span style="opacity: 0.5;">Learned <%= job.experience.join(", ") %></span>
|
||||
</p>
|
||||
</li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
</main>
|
||||
<%- include("../components/footer.ejs") %>
|
||||
<%- include("../components/post-main.ejs") %>
|
||||
</body>
|
||||
</html>
|
27
views/pages/resumeEntry.ejs
Normal file
27
views/pages/resumeEntry.ejs
Normal file
|
@ -0,0 +1,27 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<%- include("../components/page-head.ejs") %>
|
||||
<body>
|
||||
<main>
|
||||
<h1>resume</h1>
|
||||
<p>my job history, in general.</p>
|
||||
<h2>enter a key</h2>
|
||||
<p>you are attempting to access my resume.<br />
|
||||
this information is key-protected and will remain that way for some amount of time.</p>
|
||||
<form action="/resume" method="get">
|
||||
<p>
|
||||
<label for="key">Key:</label>
|
||||
<input type="password" name="key" id="key" />
|
||||
</p>
|
||||
<p>
|
||||
<input type="submit" value="Test Access" />
|
||||
</p>
|
||||
<% if (badError) { %>
|
||||
<p style="opacity: 0.5;">ERROR: incorrect key.</p>
|
||||
<% } %>
|
||||
</form>
|
||||
</main>
|
||||
<%- include("../components/footer.ejs") %>
|
||||
<%- include("../components/post-main.ejs") %>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue