commit cf4066f7dce473dc0bd56e55f286e0b62d8e5b2a
parent 43182dab4316d6c02ae4217700483355e8c6b3b9
Author: Damian Myrda <damian@prime8.dev>
Date: Fri, 3 Jan 2025 12:52:24 -0600
feat(web): single-page layout, favicon and caddy error pages
Diffstat:
15 files changed, 323 insertions(+), 232 deletions(-)
diff --git a/Dockerfile b/Dockerfile
@@ -5,10 +5,14 @@ RUN ["zola", "build"]
FROM caddy:builder AS caddy
RUN xcaddy build \
- --with github.com/caddyserver/cache-handler
+ --with github.com/caddyserver/cache-handler \
+ --with github.com/mholt/caddy-ratelimit
FROM caddy:latest
COPY --from=caddy /usr/bin/caddy /usr/bin/caddy
COPY ./caddy /etc/caddy/Caddyfile
COPY --from=web /src/public/ /web/
-EXPOSE 80 443
+EXPOSE 80 443 443/udp
+
+HEALTHCHECK --interval=60s --timeout=5s --start-period=5s \
+ CMD curl -f http://localhost/ || exit 1
diff --git a/caddy b/caddy
@@ -1,42 +1,127 @@
{
- cache
+ cache
}
+# Root
prime8.dev {
- redir https://www.prime8.dev{uri} 301
+ redir https://www.prime8.dev{uri} 301
+
+ # Security
+ header {
+ Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" # Enforce HTTPS for 1 year
+ X-Frame-Options "DENY" # Prevent embedding
+ -Server # Remove the Server header
+ Permissions-Policy "geolocation=(), microphone=(), camera=()" # No permissions
+ X-Content-Type-Options "nosniff" # Prevent MIME-type sniffing
+ Referrer-Policy "no-referrer-when-downgrade" # Control referrer behavior
+ }
+ tls damian@prime8.dev
}
+# Main Site
www.prime8.dev {
root * /web/
file_server
try_files {path} {path}.html
- cache
- handle_errors {
- @404 {
- expression {http.error.status_code} == 404
- }
- rewrite @404 /404.html
- file_server
- }
+ # 404 Page Handling
+ handle_errors {
+ @404 {
+ expression {http.error.status_code} == 404
+ }
+ rewrite @404 /404.html
+
+ @429 {
+ expression {http.error.status_code} == 429
+ }
+ rewrite @429 /429.html
+
+ @500 {
+ expression {http.error.status_code} == 500
+ }
+ rewrite @500 /500.html
- tls damian@prime8.dev
+ file_server
+ }
+
+ # Caching
+ cache
+ header /static/* {
+ Cache-Control "public, max-age=86400; immutable" # Cache for 1 day
+ }
+
+ # Security
+ header {
+ Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" # Enforce HTTPS for 1 year
+ X-Frame-Options "DENY" # Prevent embedding
+ -Server # Remove the Server header
+ Permissions-Policy "geolocation=(), microphone=(), camera=()" # No permissions
+ X-Content-Type-Options "nosniff" # Prevent MIME-type sniffing
+ Referrer-Policy "no-referrer-when-downgrade" # Control referrer behavior
+ }
+ tls damian@prime8.dev
}
+# Mail Server
mail.prime8.dev {
- tls damian@prime8.dev
+ header {
+ X-Robots-Tag "noindex, nofollow" # Prevent search engine indexing
+
+ # Security
+ Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" # Enforce HTTPS for 1 year
+ X-Frame-Options "DENY" # Prevent embedding
+ -Server # Remove the Server header
+ Permissions-Policy "geolocation=(), microphone=(), camera=()" # No permissions
+ X-Content-Type-Options "nosniff" # Prevent MIME-type sniffing
+ Referrer-Policy "no-referrer-when-downgrade" # Control referrer behavior
+ }
+ tls damian@prime8.dev
}
+# Git Server
git.prime8.dev {
- reverse_proxy http://gitweb:80
- cache
+ reverse_proxy http://gitweb:80
- tls damian@prime8.dev
+ # Caching
+ cache
+ header /static/* {
+ Cache-Control "public, max-age=86400; immutable" # Cache for 1 day
+ }
+
+ header {
+ X-Robots-Tag "noindex, nofollow" # Prevent search engine indexing
+
+ # Security
+ Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" # Enforce HTTPS for 1 year
+ X-Frame-Options "DENY" # Prevent embedding
+ -Server # Remove the Server header
+ Permissions-Policy "geolocation=(), microphone=(), camera=()" # No permissions
+ X-Content-Type-Options "nosniff" # Prevent MIME-type sniffing
+ Referrer-Policy "no-referrer-when-downgrade" # Control referrer behavior
+ }
+ tls damian@prime8.dev
}
+# CSC Website
csc.prime8.dev {
- reverse_proxy http://csc:80
- cache
+ reverse_proxy http://csc:80
- tls damian@prime8.dev
-}
+ # Caching
+ cache
+ header /static/* {
+ Cache-Control "public, max-age=86400; immutable" # Cache for 1 day
+ }
+
+ header {
+ X-Robots-Tag "noindex, nofollow" # Prevent search engine indexing
+
+ # Security
+ Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" # Enforce HTTPS for 1 year
+ X-Frame-Options "DENY" # Prevent embedding
+ -Server # Remove the Server header
+ Permissions-Policy "geolocation=(), microphone=(), camera=()" # No permissions
+ X-Content-Type-Options "nosniff" # Prevent MIME-type sniffing
+ Referrer-Policy "no-referrer-when-downgrade" # Control referrer behavior
+ }
+ tls damian@prime8.dev
+}
+\ No newline at end of file
diff --git a/compose.yml b/compose.yml
@@ -1,92 +1,58 @@
+---
services:
updates:
container_name: updates
image: containrrr/watchtower:latest
- volumes:
- - /var/run/docker.sock:/var/run/docker.sock
- command:
- - "--interval"
- - "120"
- - "--rolling-restart"
- - "--cleanup"
+ volumes: [ /var/run/docker.sock:/var/run/docker.sock ]
+ command: [ --interval, '3600', --cleanup, --rolling-restart ]
restart: always
-
web:
container_name: web
image: moncheeta/website:latest
- ports:
- - "80:80"
- - "443:443"
+ ports: [ 80:80, 443:443 ]
volumes:
- ./caddy:/etc/caddy/Caddyfile:ro
+ - ./auth.conf:/etc/caddy/auth.conf:ro
- ./web/data/:/data/caddy/
- ./web/config/:/config/caddy/
restart: always
-
mail:
container_name: mail
image: ghcr.io/docker-mailserver/docker-mailserver:latest
hostname: mail.prime8.dev
env_file: ./mail/env
- ports:
- - "25:25"
- - "143:143"
- - "465:465"
- - "587:587"
- - "993:993"
+ ports: [ 25:25, 143:143, 465:465, 587:587, 993:993 ]
volumes:
- ./mail/data/:/var/mail/
- ./mail/state/:/var/mail-state/
- ./mail/logs/:/var/log/mail/
- ./mail/config/:/tmp/docker-mailserver/
- - ./web/data/certificates/acme-v02.api.letsencrypt.org-directory/mail.prime8.dev/:/etc/certificates/:ro
- depends_on:
- - web
- cap_add:
- - NET_ADMIN
- restart: always
-
- fail2ban:
- image: crazymax/fail2ban:latest
- container_name: fail2ban
- volumes:
- - ./jail:/etc/fail2ban/jail.local:ro
- - /var/log/mail/mail.log:/var/log/mail/mail.log:ro
- - /var/log/auth.log:/var/log/auth.log:ro
+ - ./web/data/certificates/acme-v02.api.letsencrypt.org-directory/mail.prime8.dev/mail.prime8.dev.crt:/etc/certificates/cert.pem
+ - ./web/data/certificates/acme-v02.api.letsencrypt.org-directory/mail.prime8.dev/mail.prime8.dev.key:/etc/certificates/key.pem
+ depends_on: [ web ]
restart: always
-
gitolite:
container_name: gitolite
image: jgiannuzzi/gitolite:latest
env_file: ./git/env
- volumes:
- - ./git/.ssh/keys/:/etc/ssh/keys/
- - ./git/:/var/lib/git/
- ports:
- - "22:22"
+ volumes: [ ./git/.ssh/keys/:/etc/ssh/keys/, ./git/:/var/lib/git/ ]
+ ports: [ 22:22 ]
restart: always
-
gitweb:
container_name: gitweb
image: moncheeta/gitweb:latest
- depends_on:
- - gitolite
+ depends_on: [ gitolite ]
volumes:
- ./git/gitweb.conf:/etc/gitweb.conf:ro
- ./git/theme/:/usr/share/gitweb/theme/:ro
- ./git/:/srv/git/:ro
- expose:
- - "80"
+ expose: [ 80 ]
restart: always
-
csc:
container_name: csc
image: moncheeta/csc:latest
- environment:
- - DOMAIN=csc.prime8.dev
+ environment: [ DOMAIN=csc.prime8.dev ]
env_file: ./csc/env
- volumes:
- - ./csc/auth.json:/app/google_auth.json:ro
- expose:
- - "80"
+ volumes: [ ./csc/auth.json:/app/google_auth.json:ro ]
+ expose: [ 80 ]
restart: always
diff --git a/jail b/jail
@@ -1,18 +0,0 @@
-[DEFAULT]
-bantime = 10m
-findtime = 10m
-maxretry = 5
-
-[postfix]
-enabled = true
-port = smtp,ssmtp
-filter = postfix
-logpath = /var/log/mail/mail.log
-maxretry = 3
-
-[dovecot]
-enabled = true
-port = imap
-filter = dovecot
-logpath = /var/log/mail/mail.log
-maxretry = 3
diff --git a/mail/env b/mail/env
@@ -20,8 +20,8 @@ SSL_TYPE=manual
# These are only supported with `SSL_TYPE=manual`.
# Provide the path to your cert and key files that you've mounted access to within the container.
-SSL_CERT_PATH=/etc/certificates/mail.prime8.dev.crt
-SSL_KEY_PATH=/etc/certificates/mail.prime8.dev.key
+SSL_CERT_PATH=/etc/certificates/cert.pem
+SSL_KEY_PATH=/etc/certificates/key.pem
# Optional: A 2nd certificate can be supported as fallback (dual cert support), eg ECDSA with an RSA fallback.
# Useful for additional compatibility with older MTA and MUA (eg pre-2015).
SSL_ALT_CERT_PATH=
diff --git a/web/config.toml b/web/config.toml
@@ -3,5 +3,4 @@ base_url = "https://prime8.dev"
[extra]
tabs = [
{ name = "home", url = "/" },
- { name = "projects", url = "/projects" },
]
diff --git a/web/content/projects.md b/web/content/projects.md
@@ -1,32 +0,0 @@
-+++
-template = "projects.html"
-+++
-
-<div class="row-fluid">
- <div class="span4 w-100">
- <h2><a href="https://git.prime8.dev/?p=mos.git">MOS</a></h2>
- <p>In my free time, I work on MOS, my minimal operating system. This project has deepened my understanding of OS internals, and I'm planning to rewrite it for the RISC-V architecture. While it's currently quite simple, I have plans to expand its feature set in the future. My goal is to create a simple yet modern operating system without the years of technical debt. Users interact with the terminal through command line and terminal user interfaces designed for ease of use and speed.</p>
- </div>
- <div class="span4 w-100">
- <h2><a href="https://git.prime8.dev/?p=janitor.git">Janitor</a></h2>
- <p>During my summer internship at Amplifier Health, I developed a service that sorts audio files into three categories: speech, music, and noise. It started as a Python project using PyTorch and FastAPI, but I later ported it to Rust with tch-rs and Axum for better performance. I also created a command line interface that makes it easy for users to access and make use of the results. The service is designed to be fast, using efficient multithreading on the CPU and taking advantage of the GPU for highly parallel processing.</p>
- </div>
- <div class="span4 w-100">
- <h2><a href="https://github.com/moncheeta/summarizer">Summarizer</a></h2>
- <p>An experiment using a microservice architecture that transcribes and summarizes audio into text using OpenAI's API. One service acts as the frontend, allowing users to upload their audio files. Another service transcribes the audio using OpenAI's Whisper model. Finally, the last service summarizes it using GPT-4-Turbo. These services communicate through Kafka, where jobs are requested and results are received. The entire system is managed with a Docker Compose file.</p>
- </div>
-</div>
-<div class="row-fluid">
- <div class="span4 w-100">
- <h2><a href="https://github.com/Carter-Thomas/HSHacks-Project">D214 Tutor Connect</a></h2>
- <p>For HSHacks 2024, my friends and I developed a website to help students find tutors. Recognizing that not every school has a dedicated portal for peer tutoring, we created a district-wide, easily accessible platform. We used ReactJS for the user interface and Firebase for storage and authentication. In the end, we won 3rd place!</p>
- </div>
- <div class="span4 w-100">
- <h2><a href="https://git.prime8.dev/?p=csc.git">CSC Website</a></h2>
- <p>I built a website for the Computer Science Club (CSC) at Prospect High School during a mini-hackathon. The site displays the date and time of the next meeting, recent announcements, a roster of members, and ongoing discussions—all updated every hour using the Schoology API. Members can also share their projects by signing in with their Google accounts and uploading their work to a SQLite database. You can see it in action at <a href="https://csc.prime8.dev">csc.prime8.dev</a></p>
- </div>
- <div class="span4 w-100">
- <h2><a href="https://github.com/moncheeta/veteran_donations">Veterans Donation Website</a></h2>
- <p>For HSHacks 2023, my very first hackathon, I created a website for an organization that helps veterans manage donations. Donors can enter their Venmo username and specify the amount they would like to contribute. They receive a notification on their phone for confirmation, and each donor gets a thank-you email as a gesture of appreciation.</p>
- </div>
-</div>
diff --git a/web/static/img/moncheeta.png b/web/static/img/moncheeta.png
Binary files differ.
diff --git a/web/templates/404.html b/web/templates/404.html
@@ -1,31 +1,9 @@
-{% extends "base.html" %}
-
-{% import "nav.html" as nav %}
-{% block navbar %}
-{{ nav::bar(active="") }}
-{% endblock %}
+{% extends "error.html" %}
{% block title %}
<title>not found</title>
{% endblock %}
-{% block content %}
-<div id="message">
- <h1>not found</h1>
-</div>
-<style>
- #content {
- padding: 0px !important;
- }
-
- #message {
- display: flex;
- justify-content: center;
- align-items: center;
- height: 100vh;
- }
-</style>
-<script>
- _386 = { fastLoad: true };
-</script>
+{% block message %}
+<h1 id="message">not found (404)</h1>
{% endblock %}
diff --git a/web/templates/429.html b/web/templates/429.html
@@ -0,0 +1,9 @@
+{% extends "error.html" %}
+
+{% block title %}
+<title>too many requests</title>
+{% endblock %}
+
+{% block message %}
+<h1 id="message">too many requests (429)</h1>
+{% endblock %}
diff --git a/web/templates/500.html b/web/templates/500.html
@@ -0,0 +1,9 @@
+{% extends "error.html" %}
+
+{% block title %}
+<title>server error</title>
+{% endblock %}
+
+{% block message %}
+<h1 id="message">server error (500)</h1>
+{% endblock %}
diff --git a/web/templates/base.html b/web/templates/base.html
@@ -3,6 +3,14 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+
+ <title>Damian Myrda</title>
+ <link rel="icon" type="image/png" href="/img/moncheeta.png" />
+ <meta
+ name="description"
+ content="Terminal enthusiast, learning from Curious George (the curious part, not the monkey business). Systems and backend programming are my focus. Striving to turn spaghetti code into well-structured algorithms and make every line count with efficiency and creativity."
+ />
+
{% block title %}{% endblock %}
<style>
@@ -12,16 +20,22 @@
}
#content {
- padding: 24px 4px 8px 4px;
+ max-width: 800px;
+ margin: 0 auto 0 auto;
+ padding: 1rem 0 1rem 0;
}
pre {
- color: #E8E6E3 !important;
+ color: #e8e6e3 !important;
background-color: #000084 !important;
}
+
+ h3 {
+ text-align: center !important;
+ }
</style>
<link href="/css/bootstrap.css" rel="stylesheet" media="screen" />
- <link href="/css/bootstrap-responsive.css" rel="stylesheet">
+ <link href="/css/bootstrap-responsive.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery.js"></script>
<script>
diff --git a/web/templates/error.html b/web/templates/error.html
@@ -0,0 +1,29 @@
+{% extends "base.html" %}
+
+{% import "nav.html" as nav %}
+{% block navbar %}
+{{ nav::bar(active="") }}
+{% endblock %}
+
+{% block content %}
+<div>
+ {% block message %}
+ {% endblock %}
+</div>
+<style>
+ #content {
+ padding: 0px !important;
+ }
+
+ #message {
+ color: #ff5555;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 100vh;
+ }
+</style>
+<script>
+ _386 = { fastLoad: true };
+</script>
+{% endblock %}
diff --git a/web/templates/index.html b/web/templates/index.html
@@ -1,9 +1,6 @@
{% extends "base.html" %}
-{% import "nav.html" as nav %}
-{% block navbar %}
-{{ nav::bar(active="home") }}
-{% endblock %}
+{% import "projects.html" as projects %}
{% block title %}
<title>prime8.dev</title>
@@ -11,19 +8,27 @@
{% block content %}
<div id="logo">
- <div>
- <pre id="firstname">
+ <div>
+ <pre id="firstname">
_ _
| | (_)
__| | __ _ _ __ ___ _ __ _ _ __
/ _` |/ _` | '_ ` _ \| |/ _` | '_ \
| (_| | (_| | | | | | | | (_| | | | |
\__,_|\__,_|_| |_| |_|_|\__,_|_| |_|
- </pre>
- <a href="mailto:damian@prime8.dev">damian@prime8.dev</a>
- </div>
+ </pre>
+ <div style="display: table; width: 100%">
+ <div class="d-table-cell">
+ <a href="mailto:damian@prime8.dev">damian@prime8.dev</a>
+ </div>
+ <div style="display: table-cell; text-align: right">
+ <pre class="hidden-phone">chicago,</pre>
+ <a class="visible-phone" href="https://www.linkedin.com/in/damian-myrda/">linkedin</a></pre>
+ </div>
+ </div>
+ </div>
<pre id="middle" class="hidden-phone"></pre>
- <pre id="lastname" class="hidden-phone">
+ <pre id="lastname" class="hidden-phone">
_
| |
_ __ ___ _ _ _ __ __| | __ _
@@ -31,7 +36,7 @@
| | | | | | |_| | | | (_| | (_| |
|_| |_| |_|\__, |_| \__,_|\__,_|
__/ |
- |___/</pre>
+illinois |___/ <a class="align-right" href="https://www.linkedin.com/in/damian-myrda/">linkedin</a></pre>
<script>
const heights = [
document.getElementById("firstname").textContent.split("\n").length,
@@ -59,60 +64,44 @@
}
</style>
</div>
-<p class="lead" style="text-align: center">
- Terminal enthusiast, learning from Curious George (the curious part, not the
- monkey business). Systems and backend programming are my focus. Striving to
- turn spaghetti code into well-structured algorithms and make every line
- count with efficiency and creativity. Making computers follow my lead, even
- when they have their own ideas.
-</p>
-<div>
-<div style="display: table; width: 100%">
- <div style="display: table-cell; white-space: nowrap">
- <h3>works with:</h3>
- <ul>
- <li style="color: #ffc40d">zig</li>
- <li style="color: #9d261d">rust</li>
- <li style="color: #bdb7af">c</li>
- <li style="color: #049cdb">c++</li>
- <li style="color: #ffc40d">python</li>
- </ul>
- <h3>learning more about:</h3>
- <ul>
- <li style="color: #049cdb">go</li>
- <li style="color: #ffc40d">java</li>
- </ul>
- <div class="visible-phone">
- <h3>activity:</h3>
- <pre class="activity">Loading activity...</pre>
- </div>
- </div>
- <div style="display: table-cell; vertical-align: middle">
- <div style="display: flex; justify-content: center">
- <div class="hidden-phone">
- <h3>activity:</h3>
- <pre class="activity">Loading activity...</pre>
- </div>
- </div>
- </div>
+<div id="content">
+ <p class="lead" style="text-align: center">
+ Terminal enthusiast, learning from Curious George (the curious part, not the
+ monkey business). Systems and backend programming are my focus. Striving to
+ turn spaghetti code into well-structured algorithms and make every line
+ count with efficiency and creativity.
+ </p>
+ <div style="display: table; width: 100%">
+ <div class="d-table-cell">
+ <h3>techonologies:</h3>
+ <ul>
+ <li style="color: #fff">languages: <span style="color: #fefe54">zig</span>, <span style="color: #ff5555">rust</span>, <span style="color: #55ffff">go</span>, <span style="color: #bbb">c</span>, <span style="color: #00aaaa">c++</span>, <span style="color: #fefe54">python</span></li>
+ <li style="color: #fff">frameworks: <span style="color: #ff5555">tokio</span>, <span style="color: #55ff55">fastapi<span>, <span style="color: #bbb">flask</span></li>
+ <li style="color: #fff">libraries: <span style="color: #ff5555">torch</span>, <span style="color: #fe54fe">kafka</span>
+ <li style="color: #fff">tools: <span style="color: #bbb">git</span>, <span style="color: #55ff55">bash</span>, <span style="color: #aa5500">make</span>, <span style="color: #bbb">gdb</span>, <span style="color: #00aaaa">docker</span></li>
+ </ul>
+ <h3>learning:</h3>
+ <ul>
+ <li style="color: #00aaaa">typescript</li>
+ <li style="color: #a85400">java</li>
+ </ul>
+ <div class="visible-phone">
+ <h3>activity:</h3>
+ <pre class="activity" style="color: #bbb !important">Loading activity...</pre>
+ </div>
+ </div>
+ <div style="display: table-cell; vertical-align: middle">
+ <div style="display: flex; justify-content: center">
+ <div class="hidden-phone">
+ <h3>activity:</h3>
+ <pre class="activity" style="color: #bbb !important">Loading activity...</pre>
+ </div>
+ </div>
+ </div>
+ </div
+ <div>
+ {{ projects::list() }}
+ </div>
</div>
<script src="js/wakatime.js"></script>
-<style>
- #content {
- max-width: 800px !important;
- margin: auto !important;
- }
-
- @media (min-width: 1200px) {
- #content {
- margin: 0 !important;
- padding: 0 !important;
- width: 800px !important;
- position: absolute !important;
- top: 50% !important;
- left: 50% !important;
- transform: translate(-50%, -50%) !important;
- }
- }
-</style>
{% endblock %}
diff --git a/web/templates/projects.html b/web/templates/projects.html
@@ -1,16 +1,5 @@
-{% extends "base.html" %}
-
-{% import "nav.html" as nav %}
-{% block navbar %}
-{{ nav::bar(active="projects") }}
-{% endblock %}
-
-{% block title %}
-<title>projects</title>
-{% endblock %}
-
-{% block content %}
-<pre>
+{% macro list() %}
+<pre style="color: #fff !important">
_ _
(_) | |
_ __ _ __ ___ _ ___ ___| |_ ___
@@ -21,5 +10,74 @@
|_| |__/
</pre>
-{{ page.content | safe }}
-{% endblock %}
+<div class="row-fluid">
+ <div class="span4 w-100">
+ <h3>MOS</h3>
+ <p>
+ In my free time, I work on MOS, my minimal operating system. This project
+ has deepened my understanding of OS internals, and I'm planning to rewrite
+ it for the RISC-V architecture. Users interact with the terminal through
+ command line and terminal user interfaces designed for ease of use and
+ efficiency.
+ </p>
+ <a href="https://git.prime8.dev/?p=mos.git" class="btn">src</a>
+ </div>
+ <div class="span4 w-100">
+ <h3>Janitor</h3>
+ <p>
+ During my summer internship at Amplifier Health, I developed a service
+ that sorts audio files into three categories: speech, music, and noise. I
+ wrote it in Rust with tch-rs and Axum aiming for better performance. The
+ service is designed to be fast, using efficient multithreading on the CPU
+ and taking advantage of the GPU for highly parallel processing.
+ </p>
+ <a href="https://git.prime8.dev/?p=janitor.git" class="btn">src</a>
+ </div>
+ <div class="span4 w-100">
+ <h3>Summarizer</h3>
+ <p>
+ An experiment using a microservice architecture that transcribes and
+ summarizes audio into text using OpenAI's API. Each service has its own
+ function that it performs, and the services communicate with each other
+ through Kafka, where jobs are requested and results are received. The
+ entire system can be easily deployed with a Docker Compose file.
+ </p>
+ <a href="https://github.com/moncheeta/summarizer" class="btn">src</a>
+ </div>
+</div>
+<div class="row-fluid">
+ <div class="span4 w-100">
+ <h3>Tutor Connect</h3>
+ <p>
+ For HSHacks 2024, my friends and I developed a website to help students
+ find tutors. Recognizing that not every school has a dedicated portal for
+ peer tutoring, we created a district-wide, easily accessible platform. We
+ used ReactJS for the user interface and Firebase for storage and
+ authentication. In the end, we won 3rd place!
+ </p>
+ <a href="https://d214tutorconnect.prime8.dev" class="btn">src</a>
+ </div>
+ <div class="span4 w-100">
+ <h3>CSC Website</h3>
+ <p>
+ I built a website for the Computer Science Club (CSC) at Prospect High
+ School during a mini-hackathon. The site displays important information it
+ fetches from the Schoology API. Members can share their projects using
+ their Google account and uploading their work to a SQLite database.
+ </p>
+ <a href="https://git.prime8.dev/?p=csc.git" class="btn">src</a>
+ <a href="https://csc.prime8.dev" class="btn">live</a>
+ </div>
+ <div class="span4 w-100">
+ <h3>Veterans Website</h3>
+ <p>
+ For HSHacks 2023, my very first hackathon, I created a website for an
+ organization that helps veterans manage donations. Donors can use their
+ Venmo account to donate to the organization. They receive a notification
+ on their phone for confirmation, and each donor gets a thank-you email as
+ a gesture of appreciation.
+ </p>
+ <a href="https://github.com/moncheeta/veteran_donations" class="btn">src</a>
+ </div>
+</div>
+{% endmacro %}