update project entries
This commit is contained in:
4 files changed
+105
-89
No files matched your search
+56
-84
@@ -1,24 +1,26 @@
|
|||||||
{
|
{
|
||||||
"projects": [
|
"projects": [
|
||||||
{
|
{
|
||||||
"title": "Schedule Generator App",
|
"title": "Reforger Mods API",
|
||||||
"slug": "schedule-generator-app",
|
"slug": "reforger-mods-api",
|
||||||
"type": "projects",
|
"type": "projects",
|
||||||
"summary": "A desktop application to interact with my schedule generator algorithm. The algorithm is written in Python and generates master and student timetables.",
|
"summary": "A public, unofficial REST API for Arma Reforger Workshop metadata, built with caching, background refresh jobs, rate limiting, and production reliability in mind.",
|
||||||
"label": "Project",
|
"label": "Project",
|
||||||
"date": "2023-08-01",
|
"date": "2026-07-06",
|
||||||
"status": "Completed",
|
"status": "Active",
|
||||||
"github": "https://github.com/SowinskiBraeden/ScheduleGeneratorApp",
|
"github": "https://github.com/SowinskiBraeden/ReforgerWorkshopAPI",
|
||||||
"demo": null,
|
"demo": "https://api.reforgermods.net",
|
||||||
"featured": true,
|
"featured": true,
|
||||||
"order": 1,
|
"order": 1,
|
||||||
"tags": [
|
"tags": [
|
||||||
"Python",
|
"Go",
|
||||||
"Desktop App",
|
"REST API",
|
||||||
"Scheduling",
|
"Caching",
|
||||||
"Data Processing"
|
"Web Scraping",
|
||||||
|
"Observability",
|
||||||
|
"Documentation"
|
||||||
],
|
],
|
||||||
"path": "./public/content/projects/schedule-generator-app.md"
|
"path": "./public/content/projects/reforger-mods-api.md"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "myboker.org",
|
"title": "myboker.org",
|
||||||
@@ -28,7 +30,7 @@
|
|||||||
"label": "Project",
|
"label": "Project",
|
||||||
"date": "2026-06-28",
|
"date": "2026-06-28",
|
||||||
"status": "Active",
|
"status": "Active",
|
||||||
"github": null,
|
"github": "https://github.com/SowinskiBraeden/boker",
|
||||||
"demo": "https://myboker.org",
|
"demo": "https://myboker.org",
|
||||||
"featured": true,
|
"featured": true,
|
||||||
"order": 2,
|
"order": 2,
|
||||||
@@ -42,6 +44,21 @@
|
|||||||
],
|
],
|
||||||
"path": "./public/content/projects/myboker-org.md"
|
"path": "./public/content/projects/myboker-org.md"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Schedule Generator App",
|
||||||
|
"slug": "schedule-generator-app",
|
||||||
|
"type": "projects",
|
||||||
|
"summary": "A desktop application to interact with my schedule generator algorithm. The algorithm is written in Python and generates master and student timetables.",
|
||||||
|
"label": "Project",
|
||||||
|
"date": "2023-08-01",
|
||||||
|
"status": "Completed",
|
||||||
|
"github": "https://github.com/SowinskiBraeden/ScheduleGeneratorApp",
|
||||||
|
"demo": null,
|
||||||
|
"featured": true,
|
||||||
|
"order": 3,
|
||||||
|
"tags": ["Python", "Desktop App", "Scheduling", "Data Processing"],
|
||||||
|
"path": "./public/content/projects/schedule-generator-app.md"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "DayZ Reforger Bot",
|
"title": "DayZ Reforger Bot",
|
||||||
"slug": "dayz-reforger-bot",
|
"slug": "dayz-reforger-bot",
|
||||||
@@ -53,35 +70,9 @@
|
|||||||
"github": "https://github.com/SowinskiBraeden/dayz-reforger",
|
"github": "https://github.com/SowinskiBraeden/dayz-reforger",
|
||||||
"demo": null,
|
"demo": null,
|
||||||
"featured": true,
|
"featured": true,
|
||||||
"order": 3,
|
|
||||||
"tags": [
|
|
||||||
"Node.js",
|
|
||||||
"Discord.js",
|
|
||||||
"MongoDB",
|
|
||||||
"Analytics",
|
|
||||||
"Nitrado"
|
|
||||||
],
|
|
||||||
"path": "./public/content/projects/dayz-reforger-bot.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Vairo Performance Website",
|
|
||||||
"slug": "vairo-performance-website",
|
|
||||||
"type": "projects",
|
|
||||||
"summary": "A freelance website project for Vairo Performance, including site design, content structure, domain setup, and launch support for a professional online presence.",
|
|
||||||
"label": "Project",
|
|
||||||
"date": "2026-03-01",
|
|
||||||
"status": "Client Work",
|
|
||||||
"github": null,
|
|
||||||
"demo": "https://vairo-performance.com",
|
|
||||||
"featured": true,
|
|
||||||
"order": 4,
|
"order": 4,
|
||||||
"tags": [
|
"tags": ["Node.js", "Discord.js", "MongoDB", "Analytics", "Nitrado"],
|
||||||
"Freelance",
|
"path": "./public/content/projects/dayz-reforger-bot.md"
|
||||||
"Web Design",
|
|
||||||
"Domain Setup",
|
|
||||||
"Client Work"
|
|
||||||
],
|
|
||||||
"path": "./public/content/projects/vairo-performance-website.md"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Lines Police CAD",
|
"title": "Lines Police CAD",
|
||||||
@@ -95,12 +86,7 @@
|
|||||||
"demo": "https://www.linespolice-cad.com/",
|
"demo": "https://www.linespolice-cad.com/",
|
||||||
"featured": false,
|
"featured": false,
|
||||||
"order": 5,
|
"order": 5,
|
||||||
"tags": [
|
"tags": ["Node.js", "Express", "MongoDB", "Web App"],
|
||||||
"Node.js",
|
|
||||||
"Express",
|
|
||||||
"MongoDB",
|
|
||||||
"Web App"
|
|
||||||
],
|
|
||||||
"path": "./public/content/projects/lines-police-cad.md"
|
"path": "./public/content/projects/lines-police-cad.md"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -115,14 +101,24 @@
|
|||||||
"demo": null,
|
"demo": null,
|
||||||
"featured": false,
|
"featured": false,
|
||||||
"order": 6,
|
"order": 6,
|
||||||
"tags": [
|
"tags": ["HTML", "CSS", "JavaScript", "Portfolio"],
|
||||||
"HTML",
|
|
||||||
"CSS",
|
|
||||||
"JavaScript",
|
|
||||||
"Portfolio"
|
|
||||||
],
|
|
||||||
"path": "./public/content/projects/portfolio.md"
|
"path": "./public/content/projects/portfolio.md"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Vairo Performance Website",
|
||||||
|
"slug": "vairo-performance-website",
|
||||||
|
"type": "projects",
|
||||||
|
"summary": "A freelance website project for Vairo Performance, including site design, content structure, domain setup, and launch support for a professional online presence.",
|
||||||
|
"label": "Project",
|
||||||
|
"date": "2026-03-01",
|
||||||
|
"status": "Client Work",
|
||||||
|
"github": null,
|
||||||
|
"demo": "https://vairo-performance.com",
|
||||||
|
"featured": false,
|
||||||
|
"order": 7,
|
||||||
|
"tags": ["Freelance", "Web Design", "Domain Setup", "Client Work"],
|
||||||
|
"path": "./public/content/projects/vairo-performance-website.md"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "School Management API",
|
"title": "School Management API",
|
||||||
"slug": "school-management-api",
|
"slug": "school-management-api",
|
||||||
@@ -134,13 +130,8 @@
|
|||||||
"github": "https://github.com/SowinskiBraeden/school-management-api",
|
"github": "https://github.com/SowinskiBraeden/school-management-api",
|
||||||
"demo": null,
|
"demo": null,
|
||||||
"featured": false,
|
"featured": false,
|
||||||
"order": 7,
|
"order": 8,
|
||||||
"tags": [
|
"tags": ["Go", "API", "Documentation", "Education Tech"],
|
||||||
"Go",
|
|
||||||
"API",
|
|
||||||
"Documentation",
|
|
||||||
"Education Tech"
|
|
||||||
],
|
|
||||||
"path": "./public/content/projects/school-management-api.md"
|
"path": "./public/content/projects/school-management-api.md"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -154,12 +145,8 @@
|
|||||||
"github": "https://github.com/SowinskiBraeden/XP-System",
|
"github": "https://github.com/SowinskiBraeden/XP-System",
|
||||||
"demo": null,
|
"demo": null,
|
||||||
"featured": false,
|
"featured": false,
|
||||||
"order": 8,
|
"order": 9,
|
||||||
"tags": [
|
"tags": ["Web App", "Education Tech", "Gamification"],
|
||||||
"Web App",
|
|
||||||
"Education Tech",
|
|
||||||
"Gamification"
|
|
||||||
],
|
|
||||||
"path": "./public/content/projects/xp-system.md"
|
"path": "./public/content/projects/xp-system.md"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -176,13 +163,7 @@
|
|||||||
"demo": null,
|
"demo": null,
|
||||||
"featured": true,
|
"featured": true,
|
||||||
"order": 2,
|
"order": 2,
|
||||||
"tags": [
|
"tags": ["Hackathon", "RF", "Machine Learning", "Geolocation", "BCIT"],
|
||||||
"Hackathon",
|
|
||||||
"RF",
|
|
||||||
"Machine Learning",
|
|
||||||
"Geolocation",
|
|
||||||
"BCIT"
|
|
||||||
],
|
|
||||||
"path": "./public/content/updates/red-team-hackathon-2026.md"
|
"path": "./public/content/updates/red-team-hackathon-2026.md"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -197,11 +178,7 @@
|
|||||||
"demo": null,
|
"demo": null,
|
||||||
"featured": true,
|
"featured": true,
|
||||||
"order": 3,
|
"order": 3,
|
||||||
"tags": [
|
"tags": ["Computing Club", "Executive Member", "BCIT"],
|
||||||
"Computing Club",
|
|
||||||
"Executive Member",
|
|
||||||
"BCIT"
|
|
||||||
],
|
|
||||||
"path": "./public/content/updates/joined-bcit-computing-club.md"
|
"path": "./public/content/updates/joined-bcit-computing-club.md"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -216,12 +193,7 @@
|
|||||||
"demo": null,
|
"demo": null,
|
||||||
"featured": true,
|
"featured": true,
|
||||||
"order": 1,
|
"order": 1,
|
||||||
"tags": [
|
"tags": ["Leadership", "BCIT", "Community", "C4A"],
|
||||||
"Leadership",
|
|
||||||
"BCIT",
|
|
||||||
"Community",
|
|
||||||
"C4A"
|
|
||||||
],
|
|
||||||
"path": "./public/content/updates/appointed-president-c4a.md"
|
"path": "./public/content/updates/appointed-president-c4a.md"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ date: 2026-06-28
|
|||||||
status: Active
|
status: Active
|
||||||
featured: true
|
featured: true
|
||||||
order: 2
|
order: 2
|
||||||
|
github: https://github.com/SowinskiBraeden/boker
|
||||||
live: https://myboker.org
|
live: https://myboker.org
|
||||||
tags: Python, Flask, Jinja, Chart.js, PostgreSQL, Analytics
|
tags: Python, Flask, Jinja, Chart.js, PostgreSQL, Analytics
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
title: Reforger Mods API
|
||||||
|
slug: reforger-mods-api
|
||||||
|
summary: A public, unofficial REST API for Arma Reforger Workshop metadata, built with caching, background refresh jobs, rate limiting, and production reliability in mind.
|
||||||
|
label: Project
|
||||||
|
date: 2026-07-06
|
||||||
|
status: Active
|
||||||
|
featured: true
|
||||||
|
order: 1
|
||||||
|
github: https://github.com/SowinskiBraeden/ReforgerWorkshopAPI
|
||||||
|
live: https://api.reforgermods.net
|
||||||
|
tags: Go, REST API, Web Scraping, Observability, Documentation
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Arma Reforger Workshop is primarily a website, not a developer API. Panels, hosting tools, bots, and dashboards that need structured mod data have no clean way to get it. I built Reforger Mods API to fill that gap.
|
||||||
|
|
||||||
|
It is a public REST API, live at [api.reforgermods.net](https://api.reforgermods.net), that fetches and normalizes Workshop metadata into predictable, documented responses for developers. After the most recent relaunch, it started receiving identified traffic from third-party, which makes it one of the first projects I have built that is genuinely used beyond my own stack.
|
||||||
|
|
||||||
|
## What It Does
|
||||||
|
|
||||||
|
- Search and list Workshop mods with structured, filterable responses
|
||||||
|
- Retrieve detailed mod metadata normalized from raw Workshop pages
|
||||||
|
- Cache responses to avoid hammering upstream on repeated lookups
|
||||||
|
- Serve stale data while background refresh jobs revalidate it
|
||||||
|
- Return `202 Accepted` on cold cache misses so consumers are not blocked waiting on a full upstream scrape
|
||||||
|
|
||||||
|
## Reliability and API Design
|
||||||
|
|
||||||
|
A scraper-backed API has an obvious weak point: if the upstream source is slow or flaky, every request suffers. I treated reliability as a first-class concern from the start.
|
||||||
|
|
||||||
|
Cached responses return fast. When data is stale, it gets served immediately while a refresh job runs in the background. On a cold miss, the API returns `202 Accepted` with a job reference instead of making the caller wait. Rate limiting and defensive scraper error handling mean that one bad upstream page does not cascade into a broken API response.
|
||||||
|
|
||||||
|
Internal metrics track traffic, cache hit rates, refresh job behavior, and errors, so I can actually tell what the service is doing in production rather than guessing.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
The API includes public-facing documentation covering all endpoints, response shapes, cache behavior, and how to handle `202 Accepted` responses correctly. Getting the docs right felt important, if external tools are depending on this, they need to be able to understand it without digging through source code.
|
||||||
|
|
||||||
|
Live API and docs: [api.reforgermods.net](https://api.reforgermods.net)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
This project sits at the intersection of a few things I am consistently interested in: backend infrastructure, reliability engineering, and practical tooling for game communities. It is also the first project where I have had to think seriously about backwards compatibility and defensive API design — once external consumers exist, breaking changes have real consequences. That constraint pushed me to build it more carefully than I would have if it were purely internal.
|
||||||
@@ -137,12 +137,10 @@
|
|||||||
|
|
||||||
links.push(`<a class="${className} ${className}-primary" href="./entry.html?type=${encodeURIComponent(entry.type)}&slug=${encodeURIComponent(entry.slug)}">Read More</a>`);
|
links.push(`<a class="${className} ${className}-primary" href="./entry.html?type=${encodeURIComponent(entry.type)}&slug=${encodeURIComponent(entry.slug)}">Read More</a>`);
|
||||||
|
|
||||||
if (entry.github) {
|
|
||||||
links.push(`<a class="${className}" href="${escapeHtml(entry.github)}" target="_blank" rel="noopener noreferrer">GitHub</a>`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.demo) {
|
if (entry.demo) {
|
||||||
links.push(`<a class="${className}" href="${escapeHtml(entry.demo)}" target="_blank" rel="noopener noreferrer">Live</a>`);
|
links.push(`<a class="${className}" href="${escapeHtml(entry.demo)}" target="_blank" rel="noopener noreferrer">Live</a>`);
|
||||||
|
} else if (entry.github) {
|
||||||
|
links.push(`<a class="${className}" href="${escapeHtml(entry.github)}" target="_blank" rel="noopener noreferrer">GitHub</a>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return `<div class="${className.replace("card-link", "card-links")}">${links.join("")}</div>`;
|
return `<div class="${className.replace("card-link", "card-links")}">${links.join("")}</div>`;
|
||||||
@@ -316,8 +314,8 @@
|
|||||||
<p class="entry-summary">${escapeHtml(entry.summary || "")}</p>
|
<p class="entry-summary">${escapeHtml(entry.summary || "")}</p>
|
||||||
${renderTags(entry.tags || [], "entry-tag-list")}
|
${renderTags(entry.tags || [], "entry-tag-list")}
|
||||||
<div class="entry-links">
|
<div class="entry-links">
|
||||||
${entry.github ? `<a class="inline-button" href="${escapeHtml(entry.github)}" target="_blank" rel="noopener noreferrer">GitHub</a>` : ""}
|
|
||||||
${entry.demo ? `<a class="inline-button inline-button-primary" href="${escapeHtml(entry.demo)}" target="_blank" rel="noopener noreferrer">Live</a>` : ""}
|
${entry.demo ? `<a class="inline-button inline-button-primary" href="${escapeHtml(entry.demo)}" target="_blank" rel="noopener noreferrer">Live</a>` : ""}
|
||||||
|
${entry.github ? `<a class="inline-button" href="${escapeHtml(entry.github)}" target="_blank" rel="noopener noreferrer">GitHub</a>` : ""}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<article class="entry-content">
|
<article class="entry-content">
|
||||||
|
|||||||
Reference in new issue
Block a user