feat: initial prototype
Node.js CI / Lint and Test (push) Failing after 1m0s

This commit is contained in:
2025-09-22 16:45:38 -07:00
parent b5c1ec7fa0
commit 03c559b6cf
23 changed files with 8445 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
name: Node.js CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
lint:
name: Lint and Test
steps:
- name: Checkout Source Files
uses: actions/checkout@v4
- name: Use Node.js v22
uses: actions/setup-node@v4
with:
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 9
- name: Install Dependencies
run: pnpm install
- name: Lint Source Files
run: pnpm run lint
- name: Verify Build
run: pnpm run build
- name: Run Tests
run: pnpm run test
+4
View File
@@ -0,0 +1,4 @@
node_modules
prod
data/*
!data/*.yml
+8
View File
@@ -0,0 +1,8 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": [
"typescript"
],
}
+20
View File
@@ -0,0 +1,20 @@
import { readdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { parse } from "yaml";
const files = await readdir(join(import.meta.dirname, "data"));
for (const file of files) {
if (!file.endsWith(".yml")) continue;
const name = file.split(".")[0];
if (name === undefined) {
continue;
} // Handled manually
const path = join(import.meta.dirname, "data", file);
const contents = await readFile(path, "utf-8");
const object = await parse(contents);
await writeFile(join(import.meta.dirname, "data", `${name}.json`), JSON.stringify(object, null, 2), "utf-8");
}
+67
View File
@@ -0,0 +1,67 @@
{
"version": "0.2",
"language": "en-GB",
"words": [
"Abalise",
"Aeris",
"Alouette",
"altaria",
"Amari",
"Amirei",
"aurelia",
"Aureline",
"Azuliah",
"becca",
"beccalia",
"caelia",
"Calenelle",
"callista",
"cashapp",
"Caylus",
"Chibika",
"Deepgram",
"Eirene",
"Elaria",
"Elowyn",
"Elunara",
"gitea",
"hikari",
"Isolda",
"iuvo",
"Kuroko",
"Liora",
"Lynira",
"lyria",
"maribelle",
"maylin",
"Meliora",
"Mommy",
"naomis",
"nhcarrigan",
"nightsong",
"nymira",
"opencollective",
"oriana",
"pavelle",
"Rion",
"rosalia",
"ruus",
"Ruutuli",
"Rythm",
"Sema",
"Serenya",
"shortener",
"sorielle",
"Streamcord",
"Taryne",
"Technomancer",
"Thalassa",
"thessalia",
"UFCW",
"Veluna",
"verena",
"vitalia",
"VSAA's",
"Zephra"
]
}
+60
View File
@@ -0,0 +1,60 @@
- name: "OpenCollective"
description: "Our main financial ledger, all income and expenses are tracked here."
url: "https://opencollective.com/nhcarrigan"
icon: "https://opencollective.com/favicon.ico"
foreground: "#000000"
background: "#F5F5F5"
- name: "Discord"
description: "Our community hub for discussion and collaboration."
url: "https://chat.nhcarrigan.com"
icon: "https://discord.com/favicon.ico"
foreground: "#ffffff"
background: "#5865F2"
- name: "Patreon"
description: "Support us through monthly memberships."
url: "https://patreon.com/nhcarrigan"
icon: "https://www.patreon.com/favicon.ico"
foreground: "#ffffff"
background: "#F96854"
- name: "Ko-Fi"
description: "Buy us a coffee or make donations."
url: "https://ko-fi.com/nhcarrigan"
icon: "https://ko-fi.com/favicon.ico"
foreground: "#ffffff"
background: "#29ABE0"
- name: "Throne"
description: "Suggest and purchase gifts for us."
url: "https://throne.com/naomilgbt"
icon: "https://throne.com/favicon.ico"
foreground: "#ffffff"
background: "#000000"
- name: "Twitch"
description: "Subscribe to our streams."
url: "https://twitch.tv/naomilgbt"
icon: "https://twitch.tv/favicon.ico"
foreground: "#ffffff"
background: "#9146FF"
- name: "PayPal"
description: "Send direct donations."
url: "https://paypal.me/nhcarrigan"
icon: "https://paypal.com/favicon.ico"
foreground: "#003087"
background: "#009CDE"
- name: "CashApp"
description: "Send direct donations."
url: "https://cash.app/$nhcarrigan"
icon: "https://cash.app/favicon.ico"
foreground: "#000000"
background: "#00C244"
- name: "GitHub Sponsors"
description: "Sponsor us directly through GitHub."
url: "https://github.com/sponsors/nhcarrigan"
icon: "https://github.githubassets.com/favicons/favicon.svg"
foreground: "#ffffff"
background: "#171515"
- name: "Stripe"
description: "Make monthly donations directly through Stripe."
url: "https://buy.stripe.com/cN24iTfqu1j6b3afZ2"
icon: "https://stripe.com/favicon.ico"
foreground: "#ffffff"
background: "#635BFF"
+786
View File
@@ -0,0 +1,786 @@
version: 1.0.0
entity:
type: organization
role: owner
name: "NHCarrigan"
email: contact@nhcarrigan.com
webpageUrl:
url: "https://nhcarrigan.com"
logo: "https://nhcarrigan.com/logo.png"
phone: ""
description: >-
We are a software engineering and community management consulting firm built on a simple but powerful idea: technology should be ethical, inclusive, and sustainable.
At our core, we believe in breaking down barriers in tech. Too often, talented people—especially from marginalised communities—are excluded from opportunities. Were committed to changing that by creating spaces where everyone feels valued, supported, and empowered to contribute.
Just as important, we build with ethics and transparency. That means respecting user privacy, avoiding harmful tech practices, and communicating openly with our clients, partners, and communities. We want technology to serve people—not exploit them.
Were also deeply community-driven. We collaborate widely, listen to diverse voices, and push back against toxic behaviours that harm innovation and belonging. On top of that, mentorship is a huge part of what we do. We actively support and guide new developers, especially those from under-represented groups, helping them gain the skills and confidence to thrive in this industry.
In short, NHCarrigan is here to elevate voices, create ethical technologies, and foster a future where tech is sustainable, inclusive, and accessible for all.
Wed love to connect with folks who share those values—whether youre a developer, community leader, or someone who believes technology should make the world better. Lets build that future together.
projects:
- guid: rosalia-nightsong
name: Rosalia Nightsong
description: Our global logging server, which pipes logs from all of our apps into
a Discord webhook and our email inbox.
webpageUrl:
url: https://rosalia.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/rosalia-nightsong
licenses:
- Naomi Public License
tags:
- websites
- guid: translation-service
name: Translation Service
description: Our self-hosted LibreTranslate instance, which powers some of our apps
and is available for subscribers.
webpageUrl:
url: https://trans.nhcarrigan.com
repositoryUrl:
url: https://github.com/LibreTranslate/LibreTranslate
licenses:
- Naomi Public License
tags:
- websites
- guid: aria-iuvo
name: Aria Iuvo
description: A user-installable bot that allows you to translate any message into
your preferred language.
webpageUrl:
url: https://aria.nhcarrigan.com/
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/aria-iuvo
licenses:
- Naomi Public License
tags:
- community
- guid: becca-lyria
name: Becca Lyria
description: A user-installable Discord app that facilitates a solo Dungeons and
Dragons experience in private messages.
webpageUrl:
url: https://becca.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/becca-lyria
licenses:
- Naomi Public License
tags:
- community
- guid: cordelia-taryne
name: Cordelia Taryne
description: A user-installable Discord app that allows you to ask questions, generate
alt text for images, evaluate code, and more.
webpageUrl:
url: https://cordelia.nhcarrigan.com/
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/cordelia-taryne
licenses:
- Naomi Public License
tags:
- community
- guid: gwen-abalise
name: Gwen Abalise
description: A ticketing system for Discord servers.
webpageUrl:
url: https://gwen.nhcarrigan.com/
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/gwen-abalise
licenses:
- Naomi Public License
tags:
- community
- guid: maylin-taryne
name: Maylin Taryne
description: A helpful and supportive Discord bot that allows you to have conversations
with a virtual friend in private messages.
webpageUrl:
url: https://maylin.nhcarrigan.com/
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/maylin-taryne
licenses:
- Naomi Public License
tags:
- community
- guid: melody-iuvo
name: Melody Iuvo
description: A user-installable task management application for Discord.
webpageUrl:
url: https://melody.nhcarrigan.com/
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/melody-iuvo
licenses:
- Naomi Public License
tags:
- community
- guid: beccalia-origins
name: 'Beccalia: Origins'
description: Originally planned as the story of Becca and Rosalia growing up, this
game was only released as a demo.
webpageUrl:
url: https://beccalia.nhcarrigan.com/origins
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan-games/beccalia-origins
licenses:
- Naomi Public License
tags:
- apps
- guid: beccalia-prologue
name: 'Beccalia: Prologue'
description: An introductory story that sets the stage for the Beccalia universe,
featuring Becca and Rosalia.
webpageUrl:
url: https://beccalia.nhcarrigan.com/prologue
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan-games/beccalia-prologue
licenses:
- Naomi Public License
tags:
- apps
- guid: life-of-a-naomi
name: Life of a Naomi
description: A quick game that introduces who Naomi is, and provides a glimpse into
her life.
webpageUrl:
url: https://loan.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan-games/life-of-a-naomi
licenses:
- Naomi Public License
tags:
- apps
- guid: ruus-goblin-quest
name: Ruu's Goblin Quest
description: A game developed for our friend Ruu's game jam.
webpageUrl:
url: https://goblin.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan-games/ruu-goblin-quest
licenses:
- Naomi Public License
tags:
- apps
- guid: naomis-blog
name: Naomi's Blog
description: The personal musings of our founder, Naomi Carrigan.
webpageUrl:
url: https://blog.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/blog
licenses:
- Naomi Public License
tags:
- websites
- guid: nymira
name: Nymira
description: A service that allows you to claim a custom <username>.naomi.party
username for Bluesky.
webpageUrl:
url: https://naomi.party
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/nymira
licenses:
- Naomi Public License
tags:
- websites
- guid: nhcarrigan-documentation
name: NHCarrigan Documentation
description: A website outlining our policies, legal agreements, community rules,
and product information.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/docs
licenses:
- Naomi Public License
tags:
- websites
- guid: gitea
name: Gitea
description: A self-hosted Gitea instance to hold all of our source code.
webpageUrl:
url: https://git.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/.gitea
licenses:
- Naomi Public License
tags:
- websites
- guid: hikari
name: Hikari
description: This dashboard!
webpageUrl:
url: https://hikari.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/hikari
licenses:
- Naomi Public License
tags:
- websites
- guid: mommy-bot
name: Mommy Bot
description: A Discord, Slack, and Bluesky bot that provides you motherly love and
encouragement.
webpageUrl:
url: https://mommy-bot.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/mommy-bot
licenses:
- Naomi Public License
tags:
- community
- guid: mommy
name: Mommy
description: A quick web app that provides you motherly love and encouragements.
webpageUrl:
url: https://mommy.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/mommy
licenses:
- Naomi Public License
tags:
- websites
- guid: lucinda
name: Lucinda
description: A kanban-style task management site.
webpageUrl:
url: https://lucinda.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/lucinda
licenses:
- Naomi Public License
tags:
- websites
- guid: portfolio
name: Portfolio
description: Our homepage and marketing landing.
webpageUrl:
url: https://nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/portfolio
licenses:
- Naomi Public License
tags:
- websites
- guid: vitalia
name: Vitalia
description: A full-featured nutrition tracker with community-driven nutrient data.
webpageUrl:
url: https://vitalia.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/vitalia
licenses:
- Naomi Public License
tags:
- websites
- guid: octavia
name: Octavia
description: Linux-native music player application with a focus on handling large
libraries with minimal memory.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/octavia
licenses:
- Naomi Public License
tags:
- apps
- guid: maribelle
name: Maribelle
description: A Discord bot that allows you to configure daily progress huddle reminders
for your server members.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/maribelle
licenses:
- Naomi Public License
tags:
- community
- guid: sorielle
name: Sorielle
description: A Discord bot that allows servers to specify a venting channel for
automatic deletion.
webpageUrl:
url: https://sorielle.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/sorielle
licenses:
- Naomi Public License
tags:
- community
- guid: verena
name: Verena
description: A Discord bot that allows identity and age verification.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/verena
licenses:
- Naomi Public License
tags:
- community
- guid: thalassa
name: Thalassa
description: A rich presence application for Linux.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/thalassa
licenses:
- Naomi Public License
tags:
- apps
- guid: aeris
name: Aeris
description: An authentication service featuring magic links and support for multiple
social media platforms
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/aeris
licenses:
- Naomi Public License
tags:
- websites
- guid: liora
name: Liora
description: A Discord bot that allows your server members to specify 'highlight'
words, which they'll get pinged on if a message contains that word.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/liora
licenses:
- Naomi Public License
tags:
- community
- guid: thessalia
name: Thessalia
description: An RPG game on Discord
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/thessalia
licenses:
- Naomi Public License
tags:
- community
- guid: callista
name: Callista
description: A user-installable Discord bot that allows you to bookmark messages
and save a link and copy in your DMs.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/callista
licenses:
- Naomi Public License
tags:
- community
- guid: isolda
name: Isolda
description: 'Modern, sleek email client for the web or desktop'
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/isolda
licenses:
- Naomi Public License
tags:
- apps
- guid: meliora
name: Meliora
description: 'Embeddable chat widget, comment section, and full support flow utility.'
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/meliora
licenses:
- Naomi Public License
tags:
- websites
- guid: aurelia
name: Aurelia
description: Blogging platform with markdown editor
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/aurelia
licenses:
- Naomi Public License
tags:
- websites
- guid: eirene
name: Eirene
description: Website and Discord activity that allows you to participate in code
challenges competitively or collaboratively
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/eirene
licenses:
- Naomi Public License
tags:
- community
- guid: amirei
name: Amirei
description: A quick social link aggregator for 'link in bio' pages.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/amirei
licenses:
- Naomi Public License
tags:
- websites
- guid: zephra
name: Zephra
description: Micro-blogging social media platform.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/zephra
licenses:
- Naomi Public License
tags:
- websites
- guid: oriana
name: Oriana
description: Uptime monitoring tool with status pages
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/oriana
licenses:
- Naomi Public License
tags:
- websites
- guid: lyra
name: Lyra
description: A web-based API mocking tool, allowing you to create temporary endpoints
for a front-end to hit, test webhook payloads, and more!
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/lyra
licenses:
- Naomi Public License
tags:
- websites
- guid: selene
name: Selene
description: A local-only privacy-focused REST API client.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/selene
licenses:
- Naomi Public License
tags:
- apps
- guid: sybil
name: Sybil
description: A Discord bot that syndicates forum threads to an indexable website
and generates help articles based on resolved conversations.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/sybil
licenses:
- Naomi Public License
tags:
- community
- guid: calenelle
name: Calenelle
description: A group coordination app with event scheduling and such.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/calenelle
licenses:
- Naomi Public License
tags:
- websites
- guid: rowena
name: Rowena
description: Web app that allows you to create and share forms, and track responses
in a user friendly table.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/rowena
licenses:
- Naomi Public License
tags:
- websites
- guid: alouette
name: Alouette
description: A web server that allows you to set up arbitrary webhooks and format
them to post on Discord.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/alouette
licenses:
- Naomi Public License
tags:
- websites
- guid: clarion
name: Clarion
description: A Discord bot with dashboard that allows server mangers to post and
edit announcements, rules, and similar.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/clarion
licenses:
- Naomi Public License
tags:
- community
- guid: elowyn
name: Elowyn
description: A quick website that helps you format text.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/elowyn
licenses:
- Naomi Public License
tags:
- websites
- guid: evangeline
name: Evangeline
description: A Discord bot that allows you to configure canned replies, retrieve
them anywhere on discord, and easily copy + paste them into chat.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/evangeline
licenses:
- Naomi Public License
tags:
- community
- guid: theodora
name: Theodora
description: A Discord bot that generates 100 days of code reminders.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/theodora
licenses:
- Naomi Public License
tags:
- community
- guid: vivienne
name: Vivienne
description: An RSS feed reader/management site.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/vivienne
licenses:
- Naomi Public License
tags:
- websites
- guid: veluna
name: Veluna
description: Discord bot that allows you to receive and answer anonymous questions.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/veluna
licenses:
- Naomi Public License
tags:
- community
- guid: elysium
name: Elysium
description: Idle RPG in the browser.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/elysium
licenses:
- Naomi Public License
tags:
- apps
- guid: chibika
name: Chibika
description: A Discord bot that generates ascii anime girls.
webpageUrl:
url: https://chibika.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/chibika
licenses:
- Naomi Public License
tags:
- community
- guid: elaria
name: Elaria
description: Meeting schedule coordination tool.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/elaria
licenses:
- Naomi Public License
tags:
- websites
- guid: elunara
name: Elunara
description: Discord bot that allows users to proxy messages so they correctly
appear as composed by an alter.
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/elunara
licenses:
- Naomi Public License
tags:
- community
- guid: aureline
name: Aureline
description: Web app that allows you to create/upload digital badges and certifications
and grant them to users
webpageUrl:
url: https://docs.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/aureline
licenses:
- Naomi Public License
tags:
- websites
- guid: lynira
name: Lynira
description: Link shortener managed via a Discord bot.
webpageUrl:
url: https://lynira.link
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/lynira
licenses:
- Naomi Public License
tags:
- apps
- guid: altaria
name: Altaria
description: A Discord bot that reminds you to provide alt-text for images.
webpageUrl:
url: https://altaria.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/altaria
licenses:
- Naomi Public License
tags:
- community
- guid: pavelle
name: Pavelle
description: Discord bot that allows you to throw things (like cake) at your fellow
server members.
webpageUrl:
url: https://pavelle.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/pavelle
licenses:
- Naomi Public License
tags:
- community
- guid: amari
name: Amari
description: Naomi's virtual personal assistant who helps out with automation around
our Discord community.
webpageUrl:
url: https://amari.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/amari
licenses:
- Naomi Public License
tags:
- community
- guid: serenya
name: Serenya
description: Discord bot that allows you to force yourself to take a break.
webpageUrl:
url: https://serenya.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/serenya
licenses:
- Naomi Public License
tags:
- community
- guid: caelia
name: Caelia
description: Discord bot that gently reminds you to use inclusive language.
webpageUrl:
url: https://caelia.nhcarrigan.com
repositoryUrl:
url: https://git.nhcarrigan.com/nhcarrigan/caelia
licenses:
- Naomi Public License
tags:
- community
funding:
channels:
- guid: "opencollective"
type: donation
address: "https://opencollective.com/nhcarrigan"
description: "OpenCollective is our main financial ledger, all income and expenses are tracked here."
- guid: "patreon"
type: subscription
address: "https://patreon.com/nhcarrigan"
description: "Support us through monthly memberships."
- guid: "kofi"
type: donation
address: "https://ko-fi.com/nhcarrigan"
description: "Buy us a coffee or make donations."
- guid: "paypal"
type: donation
address: "https://paypal.me/nhcarrigan"
description: "Send direct donations."
- guid: "cashapp"
type: donation
address: "https://cash.app/$nhcarrigan"
description: "Send direct donations."
- guid: "github-sponsors"
type: subscription
address: "https://github.com/sponsors/NHCarrigan"
description: "Sponsor us directly through GitHub."
- guid: "stripe"
type: subscription
address: "https://buy.stripe.com/cN24iTfqu1j6b3afZ2"
description: "Make monthly donations directly through Stripe."
plans:
- guid: "monthly"
status: active
name: "Monthly Donations"
description: "Support us with a monthly donation."
amount: 5
currency: USD
frequency: monthly
channels:
- opencollective
- patreon
- github-sponsors
- stripe
- guid: "one-time"
status: active
name: "One-Time Donations"
description: "Support us with a one-time donation."
amount: 0
currency: USD
frequency: one-time
channels:
- opencollective
- kofi
- paypal
- cashapp
history:
- year: 2025
income: 0
expenses: 0
taxes: 0
currency: USD
description: "Projected budget for 2025."
+485
View File
@@ -0,0 +1,485 @@
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/rosalia.png'
category: websites
description: >-
Our global logging server, which pipes logs from all of our apps into a
Discord webhook and our email inbox.
name: Rosalia Nightsong
premium: false
url: 'https://rosalia.nhcarrigan.com'
wip: false
- avatar: null
category: websites
description: >-
Our self-hosted LibreTranslate instance, which powers some of our apps and
is available for subscribers.
name: Translation Service
premium: true
url: 'https://trans.nhcarrigan.com'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/aria.png'
category: community
description: >-
A user-installable bot that allows you to translate any message into your
preferred language.
name: Aria Iuvo
premium: true
url: 'https://aria.nhcarrigan.com/'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/becca.png'
category: community
description: >-
A user-installable Discord app that facilitates a solo Dungeons and Dragons
experience in private messages.
name: Becca Lyria
premium: true
url: 'https://becca.nhcarrigan.com'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/cordelia.png'
category: community
description: >-
A user-installable Discord app that allows you to ask questions, generate
alt text for images, evaluate code, and more.
name: Cordelia Taryne
premium: true
url: 'https://cordelia.nhcarrigan.com/'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/gwen.png'
category: community
description: A ticketing system for Discord servers.
name: Gwen Abalise
premium: true
url: 'https://gwen.nhcarrigan.com/'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/maylin.png'
category: community
description: >-
A helpful and supportive Discord bot that allows you to have conversations
with a virtual friend in private messages.
name: Maylin Taryne
premium: true
url: 'https://maylin.nhcarrigan.com/'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/melody.png'
category: community
description: A user-installable task management application for Discord.
name: Melody Iuvo
premium: true
url: 'https://melody.nhcarrigan.com/'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/beccalia.png'
category: apps
description: >-
Originally planned as the story of Becca and Rosalia growing up, this game
was only released as a demo.
name: 'Beccalia: Origins'
premium: false
url: 'https://beccalia.nhcarrigan.com/origins'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/beccalia.png'
category: apps
description: >-
An introductory story that sets the stage for the Beccalia universe,
featuring Becca and Rosalia.
name: 'Beccalia: Prologue'
premium: false
url: 'https://beccalia.nhcarrigan.com/prologue'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/profile.png'
category: apps
description: >-
A quick game that introduces who Naomi is, and provides a glimpse into her
life.
name: Life of a Naomi
premium: false
url: 'https://loan.nhcarrigan.com'
wip: false
- avatar: null
category: apps
description: A game developed for our friend Ruu's game jam.
name: Ruu's Goblin Quest
premium: false
url: 'https://goblin.nhcarrigan.com'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/profile.png'
category: websites
description: 'The personal musings of our founder, Naomi Carrigan.'
name: Naomi's Blog
premium: false
url: 'https://blog.nhcarrigan.com'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/nymira.png'
category: websites
description: >-
A service that allows you to claim a custom <username>.naomi.party username
for Bluesky.
name: Nymira
premium: true
url: 'https://naomi.party'
wip: true
- avatar: null
category: websites
description: >-
A website outlining our policies, legal agreements, community rules, and
product information.
name: NHCarrigan Documentation
premium: false
url: 'https://docs.nhcarrigan.com'
wip: true
- avatar: null
category: websites
description: A self-hosted Gitea instance to hold all of our source code.
name: Gitea
premium: false
url: 'https://git.nhcarrigan.com'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/hikari.png'
category: websites
description: This dashboard!
name: Hikari
premium: false
url: 'https://hikari.nhcarrigan.com'
wip: true
- avatar: null
category: community
description: >-
A Discord, Slack, and Bluesky bot that provides you motherly love and
encouragement.
name: Mommy Bot
premium: false
url: 'https://mommy-bot.nhcarrigan.com'
wip: false
- avatar: null
category: websites
description: A quick web app that provides you motherly love and encouragements.
name: Mommy
premium: false
url: 'https://mommy.nhcarrigan.com'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/lucinda.png'
category: websites
description: A kanban-style task management site.
name: Lucinda
premium: false
url: 'https://lucinda.nhcarrigan.com'
wip: false
- avatar: null
category: websites
description: Our homepage and marketing landing.
name: Portfolio
premium: false
url: 'https://nhcarrigan.com'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/vitalia.png'
category: websites
description: A full-featured nutrition tracker with community-driven nutrient data.
name: Vitalia
premium: true
url: 'https://vitalia.nhcarrigan.com'
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/octavia.png'
category: apps
description: >-
Linux-native music player application with a focus on handling large
libraries with minimal memory.
name: Octavia
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/maribelle.png'
category: community
description: >-
A Discord bot that allows you to configure daily progress huddle reminders
for your server members.
name: Maribelle
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/sorielle.png'
category: community
description: >-
A Discord bot that allows servers to specify a venting channel for automatic
deletion.
name: Sorielle
premium: true
url: 'https://sorielle.nhcarrigan.com'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/verena.png'
category: community
description: A Discord bot that allows identity and age verification.
name: Verena
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/thalassa.png'
category: apps
description: A rich presence application for Linux.
name: Thalassa
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/aeris.png'
category: websites
description: >-
An authentication service featuring magic links and support for multiple
social media platforms
name: Aeris
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/liora.png'
category: community
description: >-
A Discord bot that allows your server members to specify 'highlight' words,
which they'll get pinged on if a message contains that word.
name: Liora
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/thessalia.png'
category: community
description: An RPG game on Discord
name: Thessalia
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/callista.png'
category: community
description: >-
A user-installable Discord bot that allows you to bookmark messages and save
a link and copy in your DMs.
name: Callista
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/isolda.png'
category: apps
description: 'Modern, sleek email client for the web or desktop'
name: Isolda
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/meliora.png'
category: websites
description: 'Embeddable chat widget, comment section, and full support flow utility.'
name: Meliora
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/aurelia.png'
category: websites
description: Blogging platform with markdown editor
name: Aurelia
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/eirene.png'
category: community
description: >-
Website and Discord activity that allows you to participate in code
challenges competitively or collaboratively
name: Eirene
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/amirei.png'
category: websites
description: A quick social link aggregator for 'link in bio' pages.
name: Amirei
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/zephra.png'
category: websites
description: Micro-blogging social media platform.
name: Zephra
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/oriana.png'
category: websites
description: Uptime monitoring tool with status pages
name: Oriana
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/lyra.png'
category: websites
description: >-
A web-based API mocking tool, allowing you to create temporary endpoints for
a front-end to hit, test webhook payloads, and more!
name: Lyra
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/selene.png'
category: apps
description: A local-only privacy-focused REST API client.
name: Selene
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/sybil.png'
category: community
description: >-
A Discord bot that syndicates forum threads to an indexable website and
generates help articles based on resolved conversations.
name: Sybil
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/calenelle.png'
category: websites
description: A group coordination app with event scheduling and such.
name: Calenelle
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/rowena.png'
category: websites
description: >-
Web app that allows you to create and share forms, and track responses in a
user friendly table.
name: Rowena
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/alouette.png'
category: websites
description: >-
A web server that allows you to set up arbitrary webhooks and format them to
post on Discord.
name: Alouette
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/clarion.png'
category: community
description: >-
A Discord bot with dashboard that allows server mangers to post and edit
announcements, rules, and similar.
name: Clarion
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/elowyn.png'
category: websites
description: A quick website that helps you format text.
name: Elowyn
premium: false
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/evangeline.png'
category: community
description: >-
A Discord bot that allows you to configure canned replies, retrieve them
anywhere on discord, and easily copy + paste them into chat.
name: Evangeline
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/theodora.png'
category: community
description: A Discord bot that generates 100 days of code reminders.
name: Theodora
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/vivienne.png'
category: websites
description: An RSS feed reader/management site.
name: Vivienne
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/veluna.png'
category: community
description: Discord bot that allows you to receive and answer anonymous questions.
name: Veluna
premium: true
url: null
wip: true
- avatar: null
category: apps
description: Idle RPG in the browser.
name: Elysium
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/chibika.png'
category: community
description: A Discord bot that generates ascii anime girls.
name: Chibika
premium: true
url: 'https://chibika.nhcarrigan.com'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/elaria.png'
category: websites
description: Meeting schedule coordination tool.
name: Elaria
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/elunara.png'
category: community
description: >-
Discord bot that allows users to proxy messages so they correctly appear as
composed by an alter.
name: Elunara
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/aureline.png'
category: websites
description: >-
Web app that allows you to create/upload digital badges and certifications
and grant them to users
name: Aureline
premium: true
url: null
wip: true
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/lynira.png'
category: apps
description: Link shortener managed via a Discord bot.
name: Lynira
premium: true
url: 'https://lynira.link'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/altaria.png'
category: community
description: A Discord bot that reminds you to provide alt-text for images.
name: Altaria
premium: false
url: 'https://altaria.nhcarrigan.com'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/pavelle.png'
category: community
description: >-
Discord bot that allows you to throw things (like cake) at your fellow
server members.
name: Pavelle
premium: true
url: 'https://pavelle.nhcarrigan.com'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/amari.png'
category: community
description: >-
Naomi's virtual personal assistant who helps out with automation around our
Discord community.
name: Amari
premium: false
url: 'https://amari.nhcarrigan.com'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/serenya.png'
category: community
description: Discord bot that allows you to force yourself to take a break.
name: Serenya
premium: false
url: 'https://serenya.nhcarrigan.com'
wip: false
- avatar: 'https://cdn.nhcarrigan.com/new-avatars/caelia.png'
category: community
description: Discord bot that gently reminds you to use inclusive language.
name: Caelia
premium: false
url: 'https://caelia.nhcarrigan.com'
wip: false
+457
View File
@@ -0,0 +1,457 @@
name: "Naomi Carrigan"
contact: "Washington, USA | contact@nhcarrigan.com | https://nhcarrigan.com | 🏳️‍⚧️ she/her"
summary: "Passionate transfeminine technologist dedicated to building inclusive tech communities and empowering individuals to break into the field. With a rich background in community management, software engineering, and developer experience, I strive to create accessible pathways for diverse talent."
employment:
- title: "Developer Experience Engineer"
company: "Deepgram"
type: "Contract"
start_date: "May 2025"
end_date: "Present"
prior_positions:
- title: "Developer Experience Consultant"
start_date: "June 2024"
end_date: "May 2025"
- title: "Community Bot Engineer"
start_date: "July 2023"
end_date: "June 2024"
description: As a Developer Experience Engineer at Deepgram, I managed community support across multiple platforms, addressing 60% of inquiries and implementing streamlined systems for tracking developer feedback. I spearheaded documentation improvements, provided expert support for JavaScript and Python SDKs, and developed applications showcasing best practices while optimizing developer workflows. Previously, as a Community Bot Engineer, I built an advanced Discord bot with AI capabilities that automated question answering, tracked community health metrics, and facilitated cross-platform integration between Discord, GitHub, and ProductBoard.
- title: "Educational Web Developer and Community Manager"
company: "freeCodeCamp"
type: "Contract"
start_date: "December 2020"
end_date: "Present"
description: As an Educational Web Developer and Community Manager at freeCodeCamp, I led the complete redesign of the Responsive Web Design curriculum used by millions of developers worldwide, significantly improving accessibility and user engagement. I developed and deployed technical solutions including a Discord moderation bot and newsletter distribution tool that streamlined operations while managing vibrant online communities across multiple platforms. Additionally, I implemented data-driven engagement strategies that increased user participation and retention, while collaborating with leadership to align community initiatives with organizational goals and representing freeCodeCamp at external events.
- title: "Technomancer"
company: "NHCarrigan"
type: "Founder"
start_date: "December 2020"
end_date: "Present"
description: As a freelance Technomancer, I've designed custom automation solutions that directly enhance client engagement and security, including a Twitch chat bot that improved user retention through intelligent channel point integrations. I engineered a membership verification system that protects multiple communities from spam, creating safer online spaces for thousands of users. My moderation bot, now deployed across approximately 300 communities, demonstrates my ability to scale technical solutions while maintaining performance and leveraging my expertise in TypeScript, Node.js, Angular, and community management principles.
- title: "Community Manager and Infrastructure Engineer"
company: "Streamcord"
type: "Contract"
start_date: "August 2021"
end_date: "December 2024"
description: As a Community Manager and Infrastructure Engineer at Streamcord, I managed a thriving community of 50,000+ members while providing support for a bot used by over 1 million communities. I led a complete community overhaul that significantly increased member retention and activity, while also contributing to technical infrastructure through bot development support and dashboard site maintenance. I spearheaded HR initiatives including staff interviews, onboarding, and quarterly evaluations, while ensuring seamless bot operations by promptly addressing user queries and maintaining high uptime across millions of communities.
- title: "Senior Integrations Engineer"
company: "Rythm"
type: "Contract"
start_date: "April 2022"
end_date: "October 2024"
description: As a Senior Integrations Engineer at Rythm, I developed and maintained advanced Discord bots and management tools that served a community of over 300,000 members. I architected comprehensive moderation systems with automated rule enforcement, case management, and evidence logging, while also creating engagement tools including economy systems with levelling mechanics and customizable profiles. I built sophisticated analytics platforms that measured both community engagement metrics and staff performance indicators, enabling data-driven community management decisions. Additionally, I developed cross-platform integration solutions connecting Discord with Twitter and Slack, significantly enhancing the community's social media presence and engagement.
- title: "Twitch Integration Engineer"
company: "BigBadBeaver TV"
type: "Freelance"
start_date: "October 2022"
end_date: "January 2024"
description: As a Twitch Integration Engineer at Big Bad Beaver Productions, I engineered and deployed "PrivateTwigs," a custom Twitch chat bot that significantly enhanced stream management and viewer engagement through comprehensive logging functionality and data analysis tools. I designed and implemented a flexible custom redemption rewards system that integrated seamlessly with Twitch's API, enabling unique viewer interactions and increasing overall stream engagement. I optimized bot performance to handle high-traffic streams without latency while collaborating directly with streamers to gather feedback for iterative improvements, all while ensuring strict compliance with Twitch's terms of service and API guidelines.
- title: "Community Manager"
company: "TweetShift"
type: "Contract"
start_date: "January 2022"
end_date: "May 2023"
description: As a Community Manager at TweetShift, I successfully managed and supported a vast user community across 230,000+ Discord servers, demonstrating exceptional ability to operate at scale. I provided comprehensive support through timely troubleshooting, created detailed user guides, and collaborated with developers to implement user-requested improvements. I maintained a safe and inclusive environment by enforcing community guidelines, moderating content, and resolving conflicts professionally, while driving engagement through organized events, informative content creation, and recognition programs for active members.
- title: "Community Manager"
company: "4C"
type: "Contract"
start_date: "May 2022"
end_date: "November 2022"
description: As a Community Manager for 4C Discord, I orchestrated exponential growth from 1,100 to 3,350 members (205% increase) by implementing strategic engagement initiatives and creating compelling community events. I established comprehensive onboarding processes and moderated discussion channels to encourage meaningful interactions, while analysing metrics to continually improve engagement strategies. Collaborating with cross-functional teams, I aligned community initiatives with broader business objectives while ensuring a safe, inclusive environment through effective conflict resolution and community guidelines enforcement.
- title: "Community Manager and Open Source Engineer"
company: "Sema"
type: "Contract"
start_date: "May 2022"
end_date: "September 2022"
description: As Community Manager and Open Source Engineer at Sema, I spearheaded growth of our Discord community from 300 to 1,000 members (233% increase) through strategic engagement initiatives and creating an inclusive environment. I led multiple open source projects, including developing a standardized base template for new projects and building the Developer Skills Matrix ecosystem with companion Discord and Slack bots. I engineered automation tools to showcase open source opportunities, cultivated a vibrant community atmosphere, and collaborated cross-functionally to align community initiatives with business objectives, driving both user growth and product adoption.
- title: "Service Operations Manager"
company: "Safeway"
type: "Full Time"
start_date: "November 2016"
end_date: "April 2020"
prior_positions:
- title: "Person-in-Charge"
start_date: "August 2013"
end_date: "November 2016"
- title: "Produce Clerk"
start_date: "February 2010"
end_date: "August 2013"
- title: "Courtesy Clerk"
start_date: "August 2009"
end_date: "February 2010"
description: As Service Operations Manager at Safeway, I orchestrated daily operations while maintaining exceptional service standards, hiring and mentoring 50+ associates and developing new managers. I implemented comprehensive OSHA-compliant safety programs that reduced workplace incidents by 25%, while effectively resolving associate concerns and enforcing company policies. I audited weekly bookkeeping records with 99.8% accuracy, processed payroll for 120+ employees, and temporarily fulfilled Assistant Store Manager responsibilities, successfully managing store closure procedures and inventory liquidation that resulted in 15% higher-than-projected revenue recovery.
volunteer:
- title: "Discord Moderator"
company: "Virtual Insanity"
start_date: "May 2024"
end_date: "Present"
description: As a Discord Moderator at Virtual Insanity, I cultivated a secure and respectful atmosphere in an adult-only community by vigilantly monitoring discussions and ensuring compliance with established guidelines. I resolved conflicts with empathy while providing support and guidance to foster positive interactions among diverse members. I also verified identification documents to maintain community authenticity, contributing significantly to an inclusive environment where all participants could thrive.
- title: "Discord Moderator"
company: "FruitPursuits"
start_date: "March 2024"
end_date: "Present"
description: As a Discord Moderator at FruitPursuits, I provide crucial support to community members, ensuring a seamless experience by troubleshooting technical issues and enforcing community guidelines. I foster engagement among members while collaborating with fellow moderators to continuously improve the server. My work has empowered users to fully enjoy their fruit-loving journey within our growing online community.
- title: "Development Lead"
company: "Artists for Palestine"
start_date: "November 2023"
end_date: "Present"
description: As a Development Lead at Artists for Palestine, I architected and built a custom bot integrating AirTable, Trello, and Discord that streamlined our workflow and significantly boosted team productivity. I implemented an automated news aggregation system that pulls and cross-posts verified content to keep our community informed with reliable updates. Additionally, I mentored junior developers through website design and implementation, establishing coding standards and ensuring all deliverables aligned with our humanitarian mission while fostering a collaborative development culture.
- title: "Discord Moderator"
company: "Angel Rose"
start_date: "September 2023"
end_date: "Present"
description: As a Discord Moderator at AngelRose, I maintained a safe and respectful environment by actively monitoring conversations and enforcing community guidelines to prevent disruptive behavior. I successfully resolved conflicts between members, providing timely support and guidance that enhanced user experience. I contributed significantly to community growth by fostering an inclusive atmosphere, resulting in improved member retention and positive feedback about the server culture.
- title: "Discord Moderator and Platform Engineering Manager"
company: "Caylus Crew"
start_date: "June 2021"
end_date: "Present"
description: As Discord Administrator and Platform Engineering Manager at Caylus Crew, I developed custom bots that enhanced community experience, including automated birthday messages and sponsor perk management systems. I coached and trained moderators, providing quarterly staff reviews to support their professional growth and improve performance. My technical innovations and leadership directly contributed to maintaining a vibrant community environment, streamlining operations, and ensuring exceptional user experiences across the platform.
- title: "Discord Administrator and Lead Integrations Engineer"
company: "Commit Your Code"
start_date: "December 2020"
end_date: "Present"
description: As the Discord Administrator and Lead Integrations Engineer at Commit Your Code, I fostered a supportive community environment by moderating interactions and providing personalized guidance to members on their development journey. I built a robust verification system that significantly reduced compromised accounts, enhancing overall community security. My technical leadership and community management skills have been instrumental in creating a safe, collaborative space where developers can overcome coding challenges and prepare for career opportunities.
- title: "Hacktoberfest Community Moderator"
company: "DigitalOcean"
start_date: "April 2021"
end_date: "October 2024"
description: As a Hacktoberfest Community Moderator at DigitalOcean, I developed a custom bot that streamlined interactions by ensuring proper repository links and providing automated responses to FAQs, significantly enhancing participant experience. I guided developers through their open source contribution journey, helping them navigate challenges and maximize their impact during the event. By maintaining a safe, welcoming environment for developers of all skill levels, I fostered inclusive collaboration that directly contributed to the overall success and participant satisfaction of Hacktoberfest across my 3+ year tenure.
- title: "Discord Administrator and Integrations Engineer"
company: "Azuliah"
start_date: "December 2023"
end_date: "April 2024"
description: As a Discord Administrator and Integrations Engineer at Azuliah, I engineered custom integrations that streamlined moderation workflows and enhanced community management efficiency for a growing VTuber audience. I successfully trained the owner and moderation team on community management best practices, implementing conflict resolution protocols that reduced incident response time by 40%. My technical solutions and leadership directly contributed to creating a safer, more welcoming community environment, resulting in a 25% increase in active member participation during my tenure.
- title: "Discord Moderator"
company: "Rion Kuroko"
start_date: "November 2023"
end_date: "January 2024"
description: As a Discord Moderator for Rion Kuroko, I built the server from scratch, designing custom structures, channels, and rules to create an organized community environment. I provided expert technical guidance to the owner on Discord moderation best practices and implemented solutions that ensured effective server management. My strategic approach to server design and community governance established a functional, user-friendly space that positioned the VTuber's community for sustainable growth and engagement.
- title: "Senior Discord Moderator"
company: "Rythm"
start_date: "February 2022"
end_date: "July 2022"
description: As a Senior Discord Moderator at Rythm, I oversaw community moderation while mentoring the moderation team, tracking weekly staff activity to identify improvement areas and ensure team effectiveness. I actively guided moderators in skill development, helping them excel in their roles while identifying strategic opportunities to enhance staff engagement. My leadership was instrumental in maintaining a positive, safe community environment, contributing significantly to the overall health and vibrancy of Rythm's Discord community.
- title: "Technical Support Staff"
company: "TweetShift"
start_date: "September 2021"
end_date: "February 2022"
description: As Technical Support Staff at TweetShift, I promptly resolved user queries and technical issues, ensuring smooth operation of their Discord bot service. I provided expert guidance on feature utilization, troubleshot complex problems, and developed effective solutions that enhanced user satisfaction. My responsive support and clear communication contributed directly to improved product reliability and community growth during my tenure.
- title: "Discord Moderator"
company: "Rythm"
start_date: "September 2021"
end_date: "February 2022"
description: As a Discord Moderator for Rythm, I maintained a safe and welcoming environment for our vibrant arts and culture community by vigilantly monitoring conversations and ensuring compliance with guidelines. I promptly addressed disruptive behavior and implemented conflict resolution strategies that reduced incidents by 30%. My proactive approach to community management fostered an inclusive atmosphere that increased member retention and participation in community events.
- title: "Community Moderator"
company: "BattleSnake"
start_date: "June 2021"
end_date: "November 2022"
description: As a Community Moderator at BattleSnake, I ensured a safe and welcoming gaming environment while actively promoting positive member interactions. I implemented effective moderation strategies that significantly enhanced user experience and retention. I drove engagement initiatives for Summer League 2021 and developed the Caster House system, resulting in increased community participation. My efforts fostered an inclusive and vibrant atmosphere that strengthened the BattleSnake community.
- title: "Integrations Engineer"
company: "XCentric Collective"
start_date: "April 2021"
end_date: "July 2023"
description: As an Integrations Engineer at XCentric Collective, I developed a custom Discord bot with an innovative MMR system for precise Rocket League skill tracking. I implemented team formation capabilities and a matchmaking algorithm that paired users with similarly skilled opponents, increasing fairness and competition quality. I also engineered scheduling functionality that streamlined the match organization process, resulting in higher user engagement and a 30% increase in community participation over my tenure.
- title: "Technical Support Staff"
company: "Streamcord"
start_date: "March 2021"
end_date: "August 2021"
description: As Technical Support Staff at Streamcord, I triaged and debugged issues for the Streamcord Discord bot, ensuring seamless user experiences through effective problem resolution. I served as a critical liaison between users and developers, communicating user feedback that directly contributed to product improvements. Additionally, I actively moderated community spaces, fostering an inclusive environment that enhanced user satisfaction and retention during my tenure.
- title: "Discord Administrator"
company: "EddieHub"
start_date: "January 2021"
end_date: "May 2023"
description: As a Discord Administrator at EddieHub, I moderated a vibrant tech community, enforcing guidelines while creating an inclusive environment for developers. I provided personalized support and resources to members on their professional development journeys, helping them overcome technical challenges and achieve their goals. Additionally, I orchestrated community events and initiatives that increased member engagement by 30%, fostering collaboration opportunities and strengthening our community bonds.
- title: "Community Moderator"
company: "freeCodeCamp"
start_date: "June 2020"
end_date: "December 2020"
description: As a Community Moderator for freeCodeCamp, I provided technical support to users debugging code and answering curriculum questions across forum and Discord platforms. I fostered an inclusive learning environment by actively moderating discussions and enforcing community guidelines. I contributed to open-source development through GitHub issue triage and pull request reviews, helping maintain quality standards for educational content that impacts thousands of learners daily.
- title: "Shop Steward"
company: "United Food and Commercial Workers"
start_date: "September 2013"
end_date: "March 2016"
description: As a Shop Steward for UFCW Local 555, I advocated for workers' rights and represented employees in management meetings to address workplace issues. I negotiated with employer representatives to contest disciplinary actions and ensured fair treatment for all union members. My advocacy resulted in a successful NLRB charge that changed company policy across four states. I attended arbitration hearings to fight for workers' interests, consistently working toward equitable outcomes while playing a key role in upholding workers' rights and fostering a fair work environment.
- title: "Instructional Assistant"
company: "Vancouver Public Schools"
start_date: "September 2010"
end_date: "June 2014"
description: As a Teaching Assistant at Walnut Grove Elementary School, I provided essential support to the music instructor by teaching students to play various instruments, including xylophone, marimba, clarinet, trumpet, violin, piano, and recorder. I facilitated learning and skill development, helping students gain proficiency and confidence in their musical abilities. Additionally, I collaborated with the instructor in preparing lesson plans, contributing ideas to enhance the curriculum and ensure engaging, well-structured lessons that created a positive and enriching educational experience.
education:
- title: "Computer Programming"
institution: "freeCodeCamp"
type: "Certification Program"
start_date: "April 2020"
end_date: "August 2020"
description: I earned a series of certifications from freeCodeCamp over the course of five months, demonstrating my proficiency in key web development and software engineering skills. These certifications include Responsive Web Design, Front End Development Libraries, JavaScript Algorithms and Data Structures, Data Visualization, Back End Development and APIs, Quality Assurance, Scientific Computing with Python, Information Security and Quality Assurance, and Full Stack Development.
Through these intensive and comprehensive programs, I gained practical experience in various areas of web development, software engineering, and security, allowing me to create well-rounded and efficient web applications. This education has equipped me with a diverse skill set that I apply to build robust, user-friendly, and secure web solutions.
- title: "Nursing"
institution: "Western Governors University"
type: "Bachelor's Degree (not obtained)"
start_date: "August 2015"
end_date: "May 2017"
description: I pursued two years of nursing studies at Western Governors University, where I gained a solid foundation in nursing practices and healthcare. My coursework included essential subjects such as anatomy and physiology, patient care, and medical ethics, which equipped me with critical knowledge and skills for the nursing profession. Unfortunately, personal circumstances related to my divorce prevented me from completing my degree. Despite this, my experience at Western Governors University has been valuable in providing me with insights that I can apply in my professional endeavours.
- title: "Labour Law"
institution: "National Labour College"
type: "Bachelor's Degree (not obtained)"
start_date: "August 2012"
end_date: "May 2014"
description: I studied Labour Law at the National Labour College for two years, where I gained a deep understanding of workers' rights, labour relations, and employment law. My coursework covered topics such as labour unions, collective bargaining, dispute resolution, and employment regulations, providing me with a strong foundation in labour and employment issues. Although the college closed before I completed my degree, the knowledge and skills I acquired during my studies have been invaluable in my professional journey.
- title: "Education"
institution: "Washington State University"
type: "Bachelor's Degree (not obtained)"
start_date: "August 2009"
end_date: "May 2011"
description: I completed my undergraduate studies in the education program at Washington State University (WSU). During my time at WSU, I gained a strong foundation in educational theories and practices, developing skills in lesson planning, classroom management, and instructional strategies. My coursework included a range of subjects related to teaching and education, preparing me to support student learning effectively. I also had opportunities to engage in hands-on experiences, such as student teaching, which further honed my ability to work with students and adapt to diverse learning environments.
- title: "Primary Schooling"
institution: "Vancouver School of Arts and Academics"
type: "Diploma"
start_date: "September 2002"
end_date: "June 2009"
description: I attended the Vancouver School of Arts and Academics from grades 6 through 12, where I immersed myself in a rigorous academic and arts education. The school provided a comprehensive program with advanced levels of science, mathematics, and foreign language, all while promoting a strong foundation in creative disciplines.
I focused my studies in the arts, particularly in band and theatre, where I honed my skills on instruments such as clarinet, piano, bassoon, and saxophone. The immersive and interdisciplinary approach at the school prepared me for both college and my future career through a daily atmosphere of creative work, self-discipline, and dedication.
VSAA's unique curriculum, along with the influence of professional artists and community leaders, fostered my growth as a responsible and compassionate citizen with a strong understanding of multiple art forms.
certifications:
- title: "Foundational C# with Microsoft"
issuer: "freeCodeCamp"
date: "November 2024"
- title: "8 Git Commands You Should Know"
issuer: "LinkedIn"
date: "May 2024"
- title: "A Marketer's Guide to Appealing to Younger Generations"
issuer: "LinkedIn"
date: "May 2024"
- title: "AI in Business Essential Training"
issuer: "LinkedIn"
date: "May 2024"
- title: "Be the Manager People Won't Leave"
issuer: "LinkedIn"
date: "May 2024"
- title: "Becoming an Ally to All"
issuer: "LinkedIn"
date: "May 2024"
- title: "Build Your Logical Thinking Skills"
issuer: "LinkedIn"
date: "May 2024"
- title: "Building Online Communities"
issuer: "LinkedIn"
date: "May 2024"
- title: "Building a Trustworthy Reputation"
issuer: "LinkedIn"
date: "May 2024"
- title: "Business Etiquette for the Modern Workplace"
issuer: "LinkedIn"
date: "May 2024"
- title: "Creating Psychological Safety for Diverse Teams"
issuer: "LinkedIn"
date: "May 2024"
- title: "E-Commerce Modernization and Personalization for Retail"
issuer: "MongoDB"
date: "May 2024"
- title: "Enhancing Resilience"
issuer: "LinkedIn"
date: "May 2024"
- title: "Generative AI Approaches to Business Challenges"
issuer: "LinkedIn"
date: "May 2024"
- title: "Hands-On Introduction: SQL"
issuer: "LinkedIn"
date: "May 2024"
- title: "Improving Your Thinking"
issuer: "LinkedIn"
date: "May 2024"
- title: "International SEO"
issuer: "LinkedIn"
date: "May 2024"
- title: "Interpersonal Communication"
issuer: "LinkedIn"
date: "May 2024"
- title: "Key Psychological Principles for Ethical Persuasion"
issuer: "LinkedIn"
date: "May 2024"
- title: "Leadership Foundations"
issuer: "LinkedIn"
date: "May 2024"
- title: "Learning Ubuntu Desktop"
issuer: "LinkedIn"
date: "May 2024"
- title: "LinkedIn Profiles for Technical Professionals"
issuer: "LinkedIn"
date: "May 2024"
- title: "MongoDB Atlas Administrator Path"
issuer: "MongoDB"
date: "May 2024"
- title: "MongoDB Data Modelling Path"
issuer: "MongoDB"
date: "May 2024"
- title: "MongoDB Database Admin Path (Self-Managed)"
issuer: "MongoDB"
date: "May 2024"
- title: "MongoDB Node.js Developer Path"
issuer: "MongoDB"
date: "May 2024"
- title: "Performing a Technical Security Audit and Assessment"
issuer: "LinkedIn"
date: "May 2024"
- title: "Productive Creativity"
issuer: "LinkedIn"
date: "May 2024"
- title: "SEO Foundations"
issuer: "LinkedIn"
date: "May 2024"
- title: "Social Media Marketing with Facebook and Twitter"
issuer: "LinkedIn"
date: "May 2024"
- title: "Social Media Marketing: Managing Online Communities"
issuer: "LinkedIn"
date: "May 2024"
- title: "Strategic Business Analysis Essentials"
issuer: "LinkedIn"
date: "May 2024"
- title: "Unconscious Bias"
issuer: "LinkedIn"
date: "May 2024"
- title: "Using Generative AI for Performance Management"
issuer: "LinkedIn"
date: "May 2024"
- title: "Legacy Front End"
issuer: "freeCodeCamp"
date: "August 2020"
- title: "Quality Assurance"
issuer: "freeCodeCamp"
date: "July 2020"
- title: "Scientific Computing with Python"
issuer: "freeCodeCamp"
date: "July 2020"
- title: "APIs and Microservices"
issuer: "freeCodeCamp"
date: "May 2020"
- title: "Data Visualisation"
issuer: "freeCodeCamp"
date: "May 2020"
- title: "Full Stack Developer"
issuer: "freeCodeCamp"
date: "May 2020"
- title: "Fundamentals of Digital Marketing"
issuer: "Google Digital Garage"
date: "May 2020"
- title: "Information Security and Quality Assurance"
issuer: "freeCodeCamp"
date: "May 2020"
- title: "Front End Libraries"
issuer: "freeCodeCamp"
date: "April 2020"
- title: "JavaScript Algorithms and Data Structures"
issuer: "freeCodeCamp"
date: "April 2020"
- title: "Responsive Web Design"
issuer: "freeCodeCamp"
date: "April 2020"
projects:
- title: "Mommy Bot"
company: "NHCarrigan"
description: "A multi-platform bot that provides motherly words of encouragement."
date: "April 2025"
- title: "Art 4 Palestine Bot"
company: "NHCarrigan"
description: "A bot for the Art 4 Palestine charity initiative. Manages art requests and delivery, and provides a news feed."
date: "November 2023"
- title: "Cordelia Taryne"
company: "NHCarrigan"
description: "AI-powered Discord assistant that provides general information, alt-text for images, text summarisation, and more."
date: "February 2025"
- title: "Life of a Naomi"
company: "NHCarrigan"
description: "RPG Maker game that gives a brief insight into who I am."
date: "December 2024"
- title: "Moderation Bot"
company: "NHCarrigan"
description: "A general-purpose bot that provides powerful moderation tools for Discord communities."
date: "May 2024"
- title: "Gwen Abalise"
company: "NHCarrigan"
description: "A private ticketing system for Discord that leverages private threads for a clean UX."
date: "February 2025"
- title: "CamperChan"
company: "freeCodeCamp"
description: "A Discord bot that provides a variety of features for the freeCodeCamp community, including moderation and Github issue management."
date: "June 2020"
- title: "Community Syndication"
company: "Deepgram"
description: "A multi-platform tool that connects help threads from Discord and GitHub into an internal Slack, and allows discussion among the team and replying to the customer directly through the Slack thread."
date: "June 2024"
publications:
- title: "I Did It, and You Can't Too!"
company: "NHCarrigan"
description: "'Become job ready in 6 months'. 'Learn to code in 90 days'. If you've heard statements like this from various tech influencers, you're not alone. That seems to be the selling point lately - how to go from 0 to job ready as fast as possible."
date: "9 August 2024"
- title: "De-Googling Myself"
company: "NHCarrigan"
description: "Recent life events have made me more cognisant of my digital footprint and online privacy. So much so that I'm paying for one of those services that sends data removal requests automatically on your behalf. But over the weekend I realised... how much data am I still giving to the tech giants? So I sat down and spent many hours moving away from those platforms."
date: "14 May 2024"
- title: "Migrating from Windows to Ubuntu"
company: "NHCarrigan"
description: "I grew up on Windows. My very first computer ran MS-DOS. My schools all used Windows computers (except for a brief stint with the colourful iMac). I thought myself to be intimately familiar with how Windows works. Becoming a developer showed me just how wrong I was."
date: "10 May 2024"
- title: "How to Claim Your Supporter Role on Discord"
company: "freeCodeCamp"
description: "If you have donated to support freeCodeCamp's efforts, you can now claim a special Supporter role in our Discord community."
date: "12 April 2024"
- title: "Hacktoberfest 2022 Contributors"
company: "freeCodeCamp"
description: "freeCodeCamp participated in Hacktoberfest (a month-long celebration of open source) again this year. We wanted to take a moment to thank all of our wonderful contributors for the effort they put in to help us continue to improve our curriculum and learning resources."
date: "11 November 2022"
- title: "How to Set Up Your Own Mastodon Instance"
company: "freeCodeCamp"
description: "Mastodon is a decentralized, federated social media platform based on the ActivityPub protocol. It allows you to follow and interact with friends across multiple instances. In this article, you will learn how freeCodeCamp set up our own Mastodon instance - and how you can too."
date: "11 November 2022"
- title: "How to Set Up a GitHub OAuth Application"
company: "freeCodeCamp"
description: "GitHub is an incredibly useful OAuth provider, especially if you are building an application targeted toward developers. In this article, we will give you a quick rundown of how to set up a GitHub OAuth application."
date: "27 October 2022"
- title: "How to Use RegEx to Match Emoji - Discord Emotes Regular Expression Tutorial"
company: "freeCodeCamp"
description: "Emoji are special Unicode characters that render pictographs. But these characters can be very tricky to identify with regular expressions (RegEx)."
date: "13 July 2022"
- title: "Build a 100 Days of Code Discord Bot with TypeScript, MongoDB, and Discord.js 13"
company: "freeCodeCamp"
description: "The 100 Days of Code challenge is very popular among new coders and developers looking to level up their skills. It's so popular that our Discord server has an entire channel dedicated to it."
date: "31 January 2022"
- title: "How to Add Sentry to Your Node.js Project with TypeScript"
company: "freeCodeCamp"
description: "Sentry.io is an external monitoring and logging service which can help you identify and triage errors in your code. These logs provide information such as a trace stack, breadcrumbs, and (assuming this is a web application) browser data. This can help you triage issues and resolve bugs faster, with less investigative overhead."
date: "28 September 2021"
- title: "How to Use TypeScript and MongoDB to Build a 100 Days of Code Discord Bot"
company: "freeCodeCamp"
description: "The 100 Days of Code challenge is very popular among new coders and developers looking to level up their skills. It's so popular that our Discord server has an entire channel dedicated to it."
date: "22 June 2021"
- title: "What is SendGrid? SMTP Email Newsletter Tutorial"
company: "freeCodeCamp"
description: "You may have heard of the term SMTP before, and wondered what it is. SMTP is a common method for handling email messages."
date: "14 May 2021"
- title: "How to Create an Email Newsletter [2021 Tutorial] - Design, Layout, Send"
company: "freeCodeCamp"
description: "If you manage a large community, chances are you need a way to communicate updates to your members quickly and efficiently. An email newsletter can be a very effective way to do so."
date: "13 May 2021"
- title: "PowerShell Themes and Windows Terminal Colour Schemes - How to Customise Your Command Line"
company: "freeCodeCamp"
description: "I recently set up and configured Windows Terminal for my local development environment. In this article, I will walk you through the steps to configure your own Terminal."
date: "5 March 2021"
- title: "How to Build a RocketChat Bot with TypeScript"
company: "freeCodeCamp"
description: "Today I will show you how to build your own Rocket.Chat bot and test it locally. This is the same process I used to build freeCodeCamp's moderation chat bot for our community's self-hosted chat server. This code is now running in production, and lots of people are using it."
date: "7 January 2021"
- title: "How to Send an Email Newsletter with the SendGrid API"
company: "freeCodeCamp"
description: "For years, Quincy Larson sent a weekly email newsletter through freeCodeCamp's Mail for Good platform, which is powered by Amazon SES. He recently migrated this process to SendGrid. In this article, I will show you how I built a tool to accomplish this."
date: "14 December 2020"
- title: "Freelancing is Hard"
company: "NHCarrigan"
description: "One of my clients put my contract on pause this month. They dont have any work for me. Thankfully, I run a very heavy workload specifically so things like this arent quite the blow. But thats not viable for many people (and arguably unhealthy for me)."
date: "11 October 2024"
- title: "Hacktoberfest 2023 Contributors"
company: "freeCodeCamp"
description: "The freeCodeCamp community just finished participating in this year's Hacktoberfest a month-long celebration of open source. Our core team is excited to recognize all of our wonderful open source contributors. A big thank you to all of these folks for the effort they put into helping improve our curriculum and learning resources."
date: "3 November 2023"
- title: "How to Help Someone with Their Code Using the Socratic Method"
company: "freeCodeCamp"
description: "As a programming community, freeCodeCamp helps many people who have questions about their code. It can be quite tempting to simply provide the learner with the answer and move on, but thats actually detrimental to the learning process."
date: "7 January 2025"
+20
View File
@@ -0,0 +1,20 @@
import NaomisConfig from '@nhcarrigan/eslint-config';
export default [
...NaomisConfig,
{
files: ["test/**/*.spec.ts"],
rules: {
"@typescript-eslint/consistent-type-assertions": "off",
"complexity": "off",
"no-await-in-loop": "off",
"vitest/no-conditional-expect": "off",
"max-lines-per-function": "off",
"max-statements": "off",
"vitest/no-conditional-in-test": "off",
"no-console": "off",
"max-lines": "off",
"max-nested-callbacks": "off",
}
}
]
+37
View File
@@ -0,0 +1,37 @@
{
"name": "data-server",
"version": "1.0.0",
"description": "A quick web server to host the various data our sites consume.",
"main": "index.js",
"type": "module",
"scripts": {
"build": "node build.js && tsc",
"lint:ts": "eslint src test --max-warnings 0",
"lint:spelling": "cspell data/*.yml",
"lint:yaml": "yaml-validator data/*.yml",
"lint": "npm-run-all --parallel lint:*",
"spelling": "cspell src/source.yaml",
"start": "op run --env-file ./prod.env -- node ./prod/index.js",
"test": "vitest --run"
},
"keywords": [],
"author": "",
"license": "ISC",
"packageManager": "pnpm@10.17.0",
"dependencies": {
"@nhcarrigan/logger": "1.0.0",
"fastify": "5.6.1"
},
"devDependencies": {
"@nhcarrigan/eslint-config": "5.2.0",
"@nhcarrigan/typescript-config": "4.0.0",
"@types/node": "24.5.2",
"cspell": "9.2.1",
"eslint": "9.36.0",
"npm-run-all": "4.1.5",
"typescript": "5.9.2",
"vitest": "3.2.4",
"yaml": "2.8.1",
"yaml-validator": "5.1.0"
}
}
+5429
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
LOG_TOKEN="op://Environment Variables - Naomi/Alert Server/api_auth"
+19
View File
@@ -0,0 +1,19 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import fastify from "fastify";
import { buildRoutes } from "./modules/buildRoutes.js";
import { logger } from "./utils/logger.js";
const server = fastify({
logger: false,
});
await buildRoutes(server);
server.listen({ port: 9999 }, () => {
void logger.log("info", "Server started on port 9999");
});
+14
View File
@@ -0,0 +1,14 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
export type Donate = Array<{
icon: string;
name: string;
description: string;
url: string;
foreground: string;
background: string;
}>;
+62
View File
@@ -0,0 +1,62 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
export interface Funding {
version: string;
entity: {
type: string;
role: string;
name: string;
email: string;
phone: string;
description: string;
webpageUrl: {
url: string;
wellKnown: string;
};
};
projects: Array<{
guid: string;
name: string;
description: string;
webpageUrl: {
url: string;
wellKnown?: string;
};
repositoryUrl: {
url: string;
wellKnown: string;
};
licenses: Array<string>;
tags: Array<string>;
}>;
funding: {
channels: Array<{
guid: string;
type: string;
address: string;
description: string;
}>;
plans: Array<{
guid: string;
status: string;
name: string;
description: string;
amount: number;
currency: string;
frequency: string;
channels: Array<string>;
}>;
history: Array<{
year: number;
income: number;
expenses: number;
taxes: number;
currency: string;
description: string;
}>;
};
}
+15
View File
@@ -0,0 +1,15 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
export type Projects = Array<{
avatar?: string;
category: string;
description: string;
name: string;
premium: boolean;
url?: string;
wip: boolean;
}>;
+69
View File
@@ -0,0 +1,69 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
/* eslint-disable @typescript-eslint/naming-convention -- snake_case matches the YAML standard, I believe?*/
interface Employment {
title: string;
company: string;
type: string;
start_date: string;
end_date: string;
description: string;
prior_positions?: Array<{
title: string;
start_date: string;
end_date: string;
}>;
}
interface Volunteer {
title: string;
company: string;
start_date: string;
end_date: string;
description: string;
}
interface Education {
title: string;
institution: string;
type: string;
start_date: string;
end_date: string;
description: string;
}
interface Certification {
title: string;
issuer: string;
date: string;
}
interface Project {
title: string;
company: string;
description: string;
date: string;
}
interface Publication {
title: string;
company: string;
description: string;
date: string;
}
export interface Resume {
name: string;
contact: string;
summary: string;
employment: Array<Employment>;
volunteer: Array<Volunteer>;
education: Array<Education>;
certifications: Array<Certification>;
projects: Array<Project>;
publications: Array<Publication>;
}
+40
View File
@@ -0,0 +1,40 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { readdir, readFile } from "node:fs/promises";
import { join } from "node:path";
import { logger } from "../utils/logger.js";
import type { FastifyInstance } from "fastify";
/**
* Builds the routes for the Fastify instance based on the files in the data directory.
* @param fastify - The Fastify instance to build routes for.
*/
export const buildRoutes = async(fastify: FastifyInstance): Promise<void> => {
const data = await readdir(join(import.meta.dirname, "..", "..", "data"));
await Promise.all(
data.map(async(file) => {
const route = `/${file}`;
const contents = await readFile(
join(import.meta.dirname, "..", "..", "data", file),
"utf-8",
);
if (file.endsWith(".yml")) {
fastify.get(route, async(_request, response) => {
response.type("application/yaml").send(contents);
});
}
if (file.endsWith(".json")) {
fastify.get(route, async(_request, response) => {
response.type("application/json").send(contents);
});
}
await logger.log("debug", `Loaded route ${route}`);
}),
);
};
+12
View File
@@ -0,0 +1,12 @@
/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { Logger } from "@nhcarrigan/logger";
export const logger = new Logger(
"Data API",
process.env.LOG_TOKEN ?? "",
);
+787
View File
@@ -0,0 +1,787 @@
/**
* @copyright NHCarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { parse } from "yaml";
import type { Donate } from "../src/interfaces/donate.js";
import type { Funding } from "../src/interfaces/funding.js";
import type { Projects } from "../src/interfaces/projects.js";
import type { Resume } from "../src/interfaces/resume.js";
const checkUrl = async(url: string): Promise<boolean> => {
try {
const response = await fetch(url, {
headers: { origin: url },
method: "HEAD",
});
if (response.status === 429) {
// Try again after few seconds
console.log(`Rate limited on ${url}, trying again...`);
await new Promise((resolve) => {
// eslint-disable-next-line no-promise-executor-return --- HUH???
return setTimeout(resolve, 5000);
});
return checkUrl(url);
}
return response.ok;
} catch (error) {
console.error(`Error checking URL ${url}:`, error);
return false;
}
};
describe("project data", () => {
it("should match the interface", async() => {
expect.hasAssertions();
const data = await readFile(
join(import.meta.dirname, "..", "data", "projects.yml"),
"utf8",
);
const parsed = parse(data) as Projects;
expect(parsed, `Parsed projects data should be defined`).toBeDefined();
expect(
Array.isArray(parsed),
`Parsed projects data should be an array`,
).toBeTruthy();
for (const project of parsed) {
expect(project, `Project should be defined`).toBeDefined();
expect(
typeof project.name,
`Project name should be a string for project: ${
project.name ?? "unknown"
}`,
).toBe("string");
expect(
typeof project.category,
`Project category should be a string for project: ${
project.name ?? "unknown"
}`,
).toBe("string");
expect(
typeof project.description,
`Project description should be a string for project: ${
project.name ?? "unknown"
}`,
).toBe("string");
expect(
typeof project.premium,
`Project premium should be a boolean for project: ${
project.name ?? "unknown"
}`,
).toBe("boolean");
expect(
typeof project.wip,
`Project wip should be a boolean for project: ${
project.name ?? "unknown"
}`,
).toBe("boolean");
if (project.avatar) {
expect(
typeof project.avatar,
`Project avatar should be a string for project: ${
project.name ?? "unknown"
}`,
).toBe("string");
await expect(
checkUrl(project.avatar),
`Project avatar should be reachable for project: ${
project.name ?? "unknown"
}`,
).resolves.toBeTruthy();
}
if (project.url) {
expect(
typeof project.url,
`Project url should be a string for project: ${
project.name ?? "unknown"
}`,
).toBe("string");
await expect(
checkUrl(project.url),
`Project url should be reachable for project: ${
project.name ?? "unknown"
}`,
).resolves.toBeTruthy();
}
}
});
});
describe("resume data", () => {
it("should match the interface", async() => {
expect.hasAssertions();
const data = await readFile(
join(import.meta.dirname, "..", "data", "resume.yml"),
"utf8",
);
const parsed = parse(data) as Resume;
expect(parsed, `Parsed resume data should be defined`).toBeDefined();
expect(typeof parsed.name, `Resume name should be a string`).toBe("string");
expect(typeof parsed.contact, `Resume contact should be a string`).toBe(
"string",
);
expect(typeof parsed.summary, `Resume summary should be a string`).toBe(
"string",
);
expect(
Array.isArray(parsed.employment),
`Resume employment should be an array`,
).toBeTruthy();
for (const employment of parsed.employment) {
expect(employment, `Employment entry should be defined`).toBeDefined();
expect(
typeof employment.title,
`Employment title should be a string for ${
employment.company ?? "unknown company"
}`,
).toBe("string");
expect(
typeof employment.company,
`Employment company should be a string for ${
employment.title ?? "unknown position"
}`,
).toBe("string");
expect(
typeof employment.type,
`Employment type should be a string for ${
employment.company ?? "unknown company"
} - ${employment.title ?? "unknown position"}`,
).toBe("string");
expect(
typeof employment.start_date,
`Employment start_date should be a string for ${
employment.company ?? "unknown company"
} - ${employment.title ?? "unknown position"}`,
).toBe("string");
expect(
typeof employment.end_date,
`Employment end_date should be a string for ${
employment.company ?? "unknown company"
} - ${employment.title ?? "unknown position"}`,
).toBe("string");
expect(
typeof employment.description,
`Employment description should be a string for ${
employment.company ?? "unknown company"
} - ${employment.title ?? "unknown position"}`,
).toBe("string");
if (employment.prior_positions) {
expect(
Array.isArray(employment.prior_positions),
`Employment prior_positions should be an array for ${
employment.company ?? "unknown company"
}`,
).toBeTruthy();
for (const position of employment.prior_positions) {
expect(
position,
`Prior position entry should be defined for ${
employment.company ?? "unknown company"
}`,
).toBeDefined();
expect(
typeof position.title,
`Prior position title should be a string for ${
employment.company ?? "unknown company"
} - ${position.title ?? "unknown position"}`,
).toBe("string");
expect(
typeof position.start_date,
`Prior position start_date should be a string for ${
employment.company ?? "unknown company"
} - ${position.title ?? "unknown position"}`,
).toBe("string");
expect(
typeof position.end_date,
`Prior position end_date should be a string for ${
employment.company ?? "unknown company"
} - ${position.title ?? "unknown position"}`,
).toBe("string");
}
}
}
expect(
Array.isArray(parsed.volunteer),
`Resume volunteer should be an array`,
).toBeTruthy();
for (const volunteer of parsed.volunteer) {
expect(volunteer, `Volunteer entry should be defined`).toBeDefined();
expect(
typeof volunteer.title,
`Volunteer title should be a string for ${
volunteer.company ?? "unknown organization"
}`,
).toBe("string");
expect(
typeof volunteer.company,
`Volunteer company should be a string for ${
volunteer.title ?? "unknown position"
}`,
).toBe("string");
expect(
typeof volunteer.start_date,
`Volunteer start_date should be a string for ${
volunteer.company ?? "unknown organization"
} - ${volunteer.title ?? "unknown position"}`,
).toBe("string");
expect(
typeof volunteer.end_date,
`Volunteer end_date should be a string for ${
volunteer.company ?? "unknown organization"
} - ${volunteer.title ?? "unknown position"}`,
).toBe("string");
expect(
typeof volunteer.description,
`Volunteer description should be a string for ${
volunteer.company ?? "unknown organization"
} - ${volunteer.title ?? "unknown position"}`,
).toBe("string");
}
expect(
Array.isArray(parsed.education),
`Resume education should be an array`,
).toBeTruthy();
for (const education of parsed.education) {
expect(education, `Education entry should be defined`).toBeDefined();
expect(
typeof education.title,
`Education title should be a string for ${
education.institution ?? "unknown institution"
}`,
).toBe("string");
expect(
typeof education.institution,
`Education institution should be a string for ${
education.title ?? "unknown degree"
}`,
).toBe("string");
expect(
typeof education.type,
`Education type should be a string for ${
education.institution ?? "unknown institution"
} - ${education.title ?? "unknown degree"}`,
).toBe("string");
expect(
typeof education.start_date,
`Education start_date should be a string for ${
education.institution ?? "unknown institution"
} - ${education.title ?? "unknown degree"}`,
).toBe("string");
expect(
typeof education.end_date,
`Education end_date should be a string for ${
education.institution ?? "unknown institution"
} - ${education.title ?? "unknown degree"}`,
).toBe("string");
expect(
typeof education.description,
`Education description should be a string for ${
education.institution ?? "unknown institution"
} - ${education.title ?? "unknown degree"}`,
).toBe("string");
}
expect(
Array.isArray(parsed.certifications),
`Resume certifications should be an array`,
).toBeTruthy();
for (const certification of parsed.certifications) {
expect(
certification,
`Certification entry should be defined`,
).toBeDefined();
expect(
typeof certification.title,
`Certification title should be a string for ${
certification.issuer ?? "unknown issuer"
}`,
).toBe("string");
expect(
typeof certification.issuer,
`Certification issuer should be a string for ${
certification.title ?? "unknown certification"
}`,
).toBe("string");
expect(
typeof certification.date,
`Certification date should be a string for ${
certification.issuer ?? "unknown issuer"
} - ${certification.title ?? "unknown certification"}`,
).toBe("string");
}
expect(
Array.isArray(parsed.projects),
`Resume projects should be an array`,
).toBeTruthy();
for (const project of parsed.projects) {
expect(project, `Resume project entry should be defined`).toBeDefined();
expect(
typeof project.title,
`Resume project title should be a string for ${
project.company ?? "unknown company"
}`,
).toBe("string");
expect(
typeof project.company,
`Resume project company should be a string for ${
project.title ?? "unknown project"
}`,
).toBe("string");
expect(
typeof project.description,
`Resume project description should be a string for ${
project.company ?? "unknown company"
} - ${project.title ?? "unknown project"}`,
).toBe("string");
expect(
typeof project.date,
`Resume project date should be a string for ${
project.company ?? "unknown company"
} - ${project.title ?? "unknown project"}`,
).toBe("string");
}
expect(
Array.isArray(parsed.publications),
`Resume publications should be an array`,
).toBeTruthy();
for (const publication of parsed.publications) {
expect(publication, `Publication entry should be defined`).toBeDefined();
expect(
typeof publication.title,
`Publication title should be a string for ${
publication.company ?? "unknown company"
}`,
).toBe("string");
expect(
typeof publication.company,
`Publication company should be a string for ${
publication.title ?? "unknown publication"
}`,
).toBe("string");
expect(
typeof publication.description,
`Publication description should be a string for ${
publication.company ?? "unknown company"
} - ${publication.title ?? "unknown publication"}`,
).toBe("string");
expect(
typeof publication.date,
`Publication date should be a string for ${
publication.company ?? "unknown company"
} - ${publication.title ?? "unknown publication"}`,
).toBe("string");
}
});
});
describe("donate data", () => {
it("should match the interface", async() => {
expect.hasAssertions();
const data = await readFile(
join(import.meta.dirname, "..", "data", "donate.yml"),
"utf8",
);
const parsed = parse(data) as Donate;
expect(parsed, `Parsed donate data should be defined`).toBeDefined();
expect(
Array.isArray(parsed),
`Parsed donate data should be an array`,
).toBeTruthy();
for (const method of parsed) {
expect(method, `Donation method should be defined`).toBeDefined();
expect(
typeof method.name,
`Donation method name should be a string for ${
method.name ?? "unknown method"
}`,
).toBe("string");
expect(
typeof method.description,
`Donation method description should be a string for ${
method.name ?? "unknown method"
}`,
).toBe("string");
expect(
typeof method.url,
`Donation method url should be a string for ${
method.name ?? "unknown method"
}`,
).toBe("string");
expect(
typeof method.icon,
`Donation method icon should be a string for ${
method.name ?? "unknown method"
}`,
).toBe("string");
expect(
typeof method.foreground,
`Donation method foreground should be a string for ${
method.name ?? "unknown method"
}`,
).toBe("string");
expect(
typeof method.background,
`Donation method background should be a string for ${
method.name ?? "unknown method"
}`,
).toBe("string");
await expect(
checkUrl(method.url),
`Donation method url should be reachable for ${
method.name ?? "unknown method"
}`,
).resolves.toBeTruthy();
}
});
});
describe("funding data", () => {
it("should match the interface", async() => {
expect.hasAssertions();
const data = await readFile(
join(import.meta.dirname, "..", "data", "funding.yml"),
"utf8",
);
const parsed = parse(data) as Funding;
expect(parsed, `Parsed funding data should be defined`).toBeDefined();
expect(typeof parsed.version, `Funding version should be a string`).toBe(
"string",
);
expect(parsed.version, `Funding version should be "1.0.0"`).toBe("1.0.0");
expect(parsed.entity, `Funding entity should be defined`).toBeDefined();
expect(
typeof parsed.entity.type,
`Funding entity type should be a string`,
).toBe("string");
expect(
typeof parsed.entity.role,
`Funding entity role should be a string`,
).toBe("string");
expect(
typeof parsed.entity.name,
`Funding entity name should be a string`,
).toBe("string");
expect(
typeof parsed.entity.email,
`Funding entity email should be a string`,
).toBe("string");
expect(
typeof parsed.entity.phone,
`Funding entity phone should be a string`,
).toBe("string");
expect(
typeof parsed.entity.description,
`Funding entity description should be a string`,
).toBe("string");
expect(
parsed.entity.webpageUrl,
`Funding entity webpageUrl should be defined`,
).toBeDefined();
expect(
typeof parsed.entity.webpageUrl.url,
`Funding entity webpageUrl.url should be a string`,
).toBe("string");
await expect(
checkUrl(parsed.entity.webpageUrl.url),
`Funding entity webpageUrl.url should be reachable`,
).resolves.toBeTruthy();
if (parsed.entity.webpageUrl.wellKnown) {
expect(
typeof parsed.entity.webpageUrl.wellKnown,
`Funding entity webpageUrl.wellKnown should be a string`,
).toBe("string");
}
expect(
Array.isArray(parsed.projects),
`Funding projects should be an array`,
).toBeTruthy();
for (const project of parsed.projects) {
expect(project, `Funding project should be defined`).toBeDefined();
expect(
typeof project.guid,
`Funding project guid should be a string for project: ${
project.name ?? "unknown"
}`,
).toBe("string");
expect(
typeof project.name,
`Funding project name should be a string for guid: ${
project.guid ?? "unknown"
}`,
).toBe("string");
expect(
typeof project.description,
`Funding project description should be a string for project: ${
project.name ?? "unknown"
}`,
).toBe("string");
expect(
project.webpageUrl,
`Funding project webpageUrl should be defined for project: ${
project.name ?? "unknown"
}`,
).toBeDefined();
expect(
typeof project.webpageUrl.url,
`Funding project webpageUrl.url should be a string for project: ${
project.name ?? "unknown"
}`,
).toBe("string");
await expect(
checkUrl(project.webpageUrl.url),
`Funding project webpageUrl.url should be reachable for project: ${
project.name ?? "unknown"
}`,
).resolves.toBeTruthy();
if (project.webpageUrl.wellKnown) {
expect(
typeof project.webpageUrl.wellKnown,
`Funding project webpageUrl.wellKnown should be a string for project: ${
project.name ?? "unknown"
}`,
).toBe("string");
}
expect(
project.repositoryUrl,
`Funding project repositoryUrl should be defined for project: ${
project.name ?? "unknown"
}`,
).toBeDefined();
expect(
typeof project.repositoryUrl.url,
`Funding project repositoryUrl.url should be a string for project: ${
project.name ?? "unknown"
}`,
).toBe("string");
await expect(
checkUrl(project.repositoryUrl.url),
`Funding project repositoryUrl.url should be reachable for project: ${
project.name ?? "unknown"
}`,
).resolves.toBeTruthy();
if (project.repositoryUrl.wellKnown) {
expect(
typeof project.repositoryUrl.wellKnown,
`Funding project repositoryUrl.wellKnown should be a string for project: ${
project.name ?? "unknown"
}`,
).toBe("string");
}
expect(
Array.isArray(project.licenses),
`Funding project licenses should be an array for project: ${
project.name ?? "unknown"
}`,
).toBeTruthy();
for (const license of project.licenses) {
expect(
typeof license,
`License should be a string for project: ${project.name ?? "unknown"}`,
).toBe("string");
}
expect(
Array.isArray(project.tags),
`Funding project tags should be an array for project: ${
project.name ?? "unknown"
}`,
).toBeTruthy();
for (const tag of project.tags) {
expect(
typeof tag,
`Tag should be a string for project: ${project.name ?? "unknown"}`,
).toBe("string");
}
}
expect(parsed.funding, `Funding data should be defined`).toBeDefined();
expect(
Array.isArray(parsed.funding.channels),
`Funding channels should be an array`,
).toBeTruthy();
for (const channel of parsed.funding.channels) {
expect(channel, `Funding channel should be defined`).toBeDefined();
expect(
typeof channel.guid,
`Funding channel guid should be a string for channel: ${
channel.type ?? "unknown"
}`,
).toBe("string");
expect(
typeof channel.type,
`Funding channel type should be a string for guid: ${
channel.guid ?? "unknown"
}`,
).toBe("string");
expect(
typeof channel.address,
`Funding channel address should be a string for channel: ${
channel.type ?? "unknown"
}`,
).toBe("string");
expect(
typeof channel.description,
`Funding channel description should be a string for channel: ${
channel.type ?? "unknown"
}`,
).toBe("string");
}
expect(
Array.isArray(parsed.funding.plans),
`Funding plans should be an array`,
).toBeTruthy();
for (const plan of parsed.funding.plans) {
expect(plan, `Funding plan should be defined`).toBeDefined();
expect(
typeof plan.guid,
`Funding plan guid should be a string for plan: ${
plan.name ?? "unknown"
}`,
).toBe("string");
expect(
typeof plan.status,
`Funding plan status should be a string for plan: ${
plan.name ?? "unknown"
}`,
).toBe("string");
expect(
typeof plan.name,
`Funding plan name should be a string for guid: ${
plan.guid ?? "unknown"
}`,
).toBe("string");
expect(
typeof plan.description,
`Funding plan description should be a string for plan: ${
plan.name ?? "unknown"
}`,
).toBe("string");
expect(
typeof plan.amount,
`Funding plan amount should be a number for plan: ${
plan.name ?? "unknown"
}`,
).toBe("number");
expect(
typeof plan.currency,
`Funding plan currency should be a string for plan: ${
plan.name ?? "unknown"
}`,
).toBe("string");
expect(
typeof plan.frequency,
`Funding plan frequency should be a string for plan: ${
plan.name ?? "unknown"
}`,
).toBe("string");
expect(
Array.isArray(plan.channels),
`Funding plan channels should be an array for plan: ${
plan.name ?? "unknown"
}`,
).toBeTruthy();
for (const channel of plan.channels) {
expect(
typeof channel,
`Funding plan channel should be a string for plan: ${
plan.name ?? "unknown"
}`,
).toBe("string");
}
}
expect(
Array.isArray(parsed.funding.history),
`Funding history should be an array`,
).toBeTruthy();
for (const history of parsed.funding.history) {
expect(history, `Funding history entry should be defined`).toBeDefined();
expect(
typeof history.year,
`Funding history year should be a number for year: ${
history.year ?? "unknown"
}`,
).toBe("number");
expect(
typeof history.income,
`Funding history income should be a number for year: ${
history.year ?? "unknown"
}`,
).toBe("number");
expect(
typeof history.expenses,
`Funding history expenses should be a number for year: ${
history.year ?? "unknown"
}`,
).toBe("number");
expect(
typeof history.taxes,
`Funding history taxes should be a number for year: ${
history.year ?? "unknown"
}`,
).toBe("number");
expect(
typeof history.currency,
`Funding history currency should be a string for year: ${
history.year ?? "unknown"
}`,
).toBe("string");
expect(
typeof history.description,
`Funding history description should be a string for year: ${
history.year ?? "unknown"
}`,
).toBe("string");
}
});
});
describe("every project has a funding entry", () => {
it("should have a funding entry for every project", async() => {
expect.hasAssertions();
const projectData = await readFile(
join(import.meta.dirname, "..", "data", "projects.yml"),
"utf8",
);
const fundingData = await readFile(
join(import.meta.dirname, "..", "data", "funding.yml"),
"utf8",
);
const projects = parse(projectData) as Projects;
const funding = parse(fundingData) as Funding;
const fundingProjectNames = funding.projects.map((project) => {
return project.name;
});
for (const project of projects) {
if (project.premium) {
expect(
fundingProjectNames,
`Premium project ${project.name} should have a funding entry`,
).toContain(
project.name,
);
}
}
});
});
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "@nhcarrigan/typescript-config",
"compilerOptions": {
"outDir": "./prod",
"rootDir": "./src"
},
"exclude": ["test", "vitest.config.ts"]
}
+7
View File
@@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
testTimeout: 120_000
}
});