Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions content/projects/cs2-arbitrage-monitor.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
title: 'CS2 Arbitrage Monitor'
description: 'A Python data pipeline that tracks completed-sale prices for CS2 skins across marketplaces and alerts on profitable spreads.'
date: '2026-05-20'
tags: ['python', 'data-pipeline', 'scraping', 'automation']
featured: false
category: Tools
role: 'Solo project'
cover: '/projects/cs2-arbitrage-monitor.svg'
---

Skin prices for the same CS2 item can differ noticeably between Steam and other
skin marketplaces, but spotting the gap by hand means watching several tabs at
once. I built a data pipeline that watches for me and pings a Discord channel
when a real arbitrage window opens.

The system runs in four stages. **Connectors** pull data per marketplace — a mix
of scrapers and official API feeds. A **sales price engine** normalizes item
names, deduplicates records, and stores historical prices. A **matcher** applies
configurable spread rules to find items priced apart across venues. **Alerts**
post an embed card to Discord with the item, per-marketplace sale prices and
timestamps, the absolute and percentage spread, and links to each listing.

## Stack

- **Python** — connectors, the price engine, and the matcher
- **Per-marketplace connectors** — scraping for some venues, official APIs for others
- **Storage layer with migrations** — historical completed-sale prices
- **Declarative rule files** in `src/rules/` — minimum spread, item filters, cooldowns
- **Discord webhook** — embed-card alerts to a single channel

## Interesting decisions

I track **completed-sale prices** — realized transactions — rather than
listing or asking prices. What a seller hopes to get is not what an item is
worth; only closed sales give an honest arbitrage signal.

Detection logic lives in **declarative rule files**, not code. Spread
thresholds, item filters, and cooldowns are all config, so I can tune what
counts as an opportunity without editing the pipeline.

The whole thing is compliance-first: it respects each platform's terms of
service, robots.txt, and rate limits, collects only public completed-sale data,
and performs no automated purchasing or trading.
39 changes: 39 additions & 0 deletions content/projects/esmer-market-accounting.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
title: 'Esmer Market Accounting'
description: 'A unified inventory, invoicing, and accounting system for a family Turkish grocery, shipped as both a web app and a Windows desktop app.'
date: '2026-05-15'
tags: ['react', 'node', 'electron', 'sql-server']
featured: false
category: Desktop
role: 'Solo project'
cover: '/projects/esmer-market-accounting.svg'
---

Our family grocery kept stock counts, invoices, and the books in separate
places that never reconciled on their own. Esmer Market Accounting folds all of
it into one Turkish-language system a non-technical shop can run at the counter.

Four modules. Inventory tracks products, SKUs, categories, suppliers, and stock
transactions. Invoicing takes manual entry plus PDF and image uploads, running
OCR extraction into a status-tracked document archive. Accounting
does multi-currency bookkeeping (TRY/USD/EUR with exchange rates), VAT
reporting, P&L, balance sheet, and cash flow. Analytics rolls it up into
dashboard KPIs and revenue and profit trends. Access is split across admin,
accountant, and cashier roles.

## Stack

- **React 18 + React Router** — Tailwind, Bootstrap, React Query, i18next (Turkish)
- **Node.js + Express** — JWT auth, Joi validation, Winston logging, Multer uploads
- **Microsoft SQL Server** — raw `mssql` driver over a Turkish schema (STOK, FIS, CARI)
- **Electron + electron-builder** — packaged as an NSIS installer and portable build

## Interesting decisions

- I dropped the ORM and wrote raw SQL over the `mssql` driver. The schema is
Turkish-named (STOK for products, FIS/FIS_DETAY for invoices, CARI for
customers), and direct queries kept full control over how SQL Server was hit.
- One React/Express codebase ships two ways: a web app, and a packaged Windows
desktop app so the shop can install and run it locally on the counter machine.
- Multi-currency accounting with exchange rates, plus OCR on uploaded
invoices so paper documents land straight in the archive.
35 changes: 35 additions & 0 deletions content/projects/esmer-market-website.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
title: 'Esmer Market'
description: 'The official site for my family grocery store in Northern Cyprus — Turkish-first, statically exported, with client-side Firebase for reviews and campaigns.'
date: '2026-06-10'
tags: ['nextjs', 'react', 'typescript', 'firebase', 'i18n']
featured: false
category: Web
role: 'Solo project'
cover: '/projects/esmer-market-website.svg'
---

Esmer Market is my family's local grocery store in Yeni Boğaziçi, Gazimağusa,
Northern Cyprus. I built the store its own website so regulars can check
campaigns, leave reviews, and find us without going through a social feed.

The site covers the essentials: home, about, contact, a gallery, a store map
for the location, customer reviews, and the usual privacy and terms pages, plus
an admin login for managing content. It reads Turkish-first, with English as the
secondary language.

## Stack

- **Next.js 15 (App Router, `output: 'export'`)** + **React 19** + **TypeScript**
- **Tailwind CSS v4** with **shadcn/ui** (new-york style)
- **Firebase** — Firestore + Auth, client-side only, for reviews and campaigns
- **i18next / react-i18next** — Turkish default, English secondary
- **Framer Motion** for transitions; deployed static on **Netlify**

## Interesting decisions

The whole site ships as a static export, yet it's still dynamic where it counts:
Firebase runs entirely client-side, so reviews and campaigns update live without
any server to host. The one non-static piece is the contact form — a Netlify
serverless function using nodemailer and reCAPTCHA v3, which keeps spam out and
email credentials off the client without standing up a backend.
38 changes: 38 additions & 0 deletions content/projects/event-management-system.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
title: 'Event Management System'
description: 'A multi-tenant app for hotel staff to schedule and track events across rooms and venues, backed by one PostgreSQL database.'
date: '2026-06-15'
tags: ['react', 'node', 'postgres', 'prisma', 'auth0']
featured: false
category: Web
role: 'Solo project'
cover: '/projects/event-management-system.svg'
---

Hotel staff were juggling weddings, conferences, and private parties across
rooms and venues, with double-bookings a constant risk. I built a single app
that puts every event on one shared calendar and keeps one hotel's data walled
off from the next.

The frontend is a React calendar with month, week, and day views, color-coded by
category, plus revenue, occupancy, and peak-time charts. Behind it, an Express
API talks to PostgreSQL through Prisma, with Auth0 handling sign-in and Winston
recording what happened.

## Stack

- **React 18 + React Router** — SPA with React Big Calendar and Recharts
- **Node.js / Express + Prisma** — REST API over one PostgreSQL database
- **Auth0** — authentication with JWT validation on every request
- **Helmet, CORS, rate limiting, input sanitization** — baseline hardening
- **Electron + PM2 / Nginx** — desktop wrapper and production process management

## Interesting decisions

**Multi-tenant with three-tier RBAC.** Every row is scoped to a hotel, and roles
run Super Admin, Hotel Admin, and User. A hotel admin only ever queries their own
hotel's events — isolation lives in the data layer, not the UI.

**Soft-delete over hard-delete.** Cancelled events are flagged, never dropped, so
there is always an audit trail. The same booking data feeds automatic conflict
detection, which flags two events fighting over the same room before they save.
103 changes: 103 additions & 0 deletions public/projects/cs2-arbitrage-monitor.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading