Warehouse Management System
WIP
(2026)
C#
ASP.NET Core
EF Core
ASP.NET Identity
Scrutor
Serilog
FluentValidation
FluentAssertions
xUnit v3
Testcontainers
PostgreSQL
Docker
Azure
TypeScript
Vue 3
Pinia
TanStack Query
VeeValidate
Zod
PrimeVue
Tailwind CSS
Chart.js
ApexCharts

🔴 Live demo → https://wms.damian-z.uk/

A warehouse management system covering the full inbound → storage → outbound cycle: product and location master data, lot and expiry tracking, multi-dimensional location capacity, planned putaway and picking, an immutable stock-movement ledger, and an analytics dashboard.

The backend is an ASP.NET Core (.NET 10) Web API built with a clean, layered architecture. The frontend is a Vue 3 single-page app. Data is stored in PostgreSQL.


Master data
  • Products — SKU, description, category, required temperature zone, unit weight and volume (used to compute location load).
  • Product categories — a hierarchical tree (parent/child), browsable and reorderable.
  • Locations — code and a structured address, type (Storage / Quarantine / Returns), temperature zone (Ambient / Chilled / Frozen), capacity in three dimensions (units / weight / volume, each capped independently, with the most restrictive one deciding), mixed-SKU and mixed-lot rules, active and blocked states, and preferred products per location.
  • Lots — lot number and expiry date for batch and expiry tracking.
Inventory
  • On-hand / reserved / available quantities tracked per location + product + lot.
  • Manual inventory adjustments (with an audit trail) and availability lookups.
Inbound — Stock-In (receiving and putaway)
  • Lifecycle: Draft → Putaway → Completed, cancellable from Draft or Putaway.
  • Placements are planned automatically by a putaway planner. Available strategies: PreferredLocation, ConsolidateSameLot, ConsolidateSameSku, Proximity, NearestEmpty, NearestAvailable.
  • In Draft the placements can be manually overridden or re-planned.
  • Capacity is reserved when putaway starts (row-locked), so concurrent receipts can't oversubscribe a location.
  • Putaway is confirmed item by item, and cancellation releases the reservations and reverses already-placed stock.
Outbound — Stock-Out (picking)
  • Lifecycle: Draft → Picking → Completed, cancellable.
  • Pick allocations across locations and lots are planned by a picking planner. Strategies: FEFO (first-expired), FIFO, LIFO, LeastQuantity, plus a Manual override.
  • In Draft the pick locations can be edited and re-planned. Picking is confirmed item by item, and cancellation returns the reserved stock.
Stock transfers and movement ledger
  • Moving stock between locations.
  • Every quantity change (stock-in, stock-out, transfer, adjustment, and their cancellations) writes an immutable StockMovement record, created as a side effect through domain events. This gives a complete, append-only audit trail.
Dashboard and admin
  • A dashboard with overview, inbound, outbound, inventory and capacity tabs (charts via Chart.js / ApexCharts).
  • Authentication and roles: JWT login with three roles — Admin, Manager, Worker. All endpoints require authentication by default, and write operations require the Admin or Manager role.
  • An admin panel: user management (create, list, delete, change passwords) and database tools (seed demo data / truncate).
Tests
  • Over 220 test cases (xUnit v3 + FluentAssertions), split between domain unit tests and application-layer integration tests.
  • Unit (no database) — stock-in and stock-out state machines, on-hand / reserved / available quantity arithmetic, location acceptance rules (capacity, temperature zone, mixed SKU and lot), and the putaway and picking planners with their strategies.
  • Integration — command and query handlers run against a real PostgreSQL container (Testcontainers), each test on a truncated database: full stock-in, stock-out, transfer and adjustment flows, capacity reservations, rollback on cancellation, movement-ledger writes through domain events, failure cases, and two stock-ins racing for the same location.
Job Offer Aggregator
(2026)
Source code
Python
FastAPI
SQLAlchemy
Pydantic
Playwright
BeautifulSoup
SQLite
TypeScript
Vue 3
PrimeVue
Docker
OVHCloud

A job-offer aggregator for the Belgian market. A set of scrapers collects postings from five portals, stores them in a shared database with deduplication and shows them in a single panel with filters. Runs start automatically on a schedule managed from the app, or manually with one button.

A commissioned project, deployed on the client's server (OVHCloud) as a set of Docker containers.

The backend is a FastAPI service (SQLAlchemy + Pydantic, SQLite, Alembic migrations). The frontend is a Vue 3 single-page app with TypeScript and PrimeVue.


Offer sources
  • Jooble — official API.
  • VDAB (the Flemish public employment service) — fetched with Playwright.
  • poloniusz.pl, axintor.be, impact.be — HTML listing parsing (requests + BeautifulSoup).
  • Each source has its own scraper behind a shared interface. Scrapers never touch the database: they return validated offers, while saving and deduplication are handled by the runner.
  • Deduplication works on a source:external_id key with a UNIQUE index, so repeated runs only add new postings.
  • A failing scraper doesn't stop the others, and the full traceback is recorded in the run history.
Panel
  • Offers — server-side paginated table with sorting and filters (source, position, location, company, title fragment, publication date); new postings are flagged.
  • History — scraper runs grouped into batches, with status, found/new counts and error details.
  • Positions — a dictionary of searched job titles (PL/NL/FR name + per-portal listing URLs), editable from the panel.
  • Schedule — automatic run times added and toggled without touching server configuration.
Running & deployment
  • The scheduler service doesn't scrape on its own. At the scheduled time it calls the API, so the automation and the panel button share a single lock and runs never overlap.
  • Password login with JWT tokens. The API isn't publicly exposed: nginx handles the traffic, serving the panel and proxying requests to the backend.
  • The whole stack starts with a single docker compose up. The database and the positions dictionary live in a volume, so rebuilding images leaves them untouched, and the schema is created by migrations executed on API startup.
Portfolio Template
(2026)
TypeScript
Angular
RxJS
SCSS
Bootstrap
GLightbox
JSON
Markdown

A portfolio website template built on the Angular framework. The application has no backend: all content comes from files in the public/assets directory, so creating your own portfolio comes down to replacing them, without digging into the source code. The whole thing is built as a static site (SSG), meaning every page in every language is rendered to finished HTML at build time and can be served from a CDN, with no application server at all.

Structured data (profile, experience, projects, technologies, site settings) is described in JSON files, while longer workplace and project descriptions live in Markdown files, one per language.


Portfolio sections
  • About
    • profile — avatar, GitHub link, header text and a short description
    • technology stack — programming languages, frameworks and libraries, tools and environment
    • education — university, field of study, years of study
    • certificates — certificate name, issuer, issue date
    • spoken languages
  • Experience — a list of workplaces: company logo with a link, years of collaboration (including a "present" variant), technology stack and a description of responsibilities.
  • Projects — a list of projects: year, technology stack, description, media gallery and a repository link. Closed-source projects are marked with a padlock, and those still in progress with a WIP badge.
  • Contact — email address.
Features
  • Prerendering (SSG) — the outputMode: static mode produces static HTML for every route and every language. Search engines and link previews get finished content instead of an empty <app-root>, and in the browser the page hydrates into a full SPA. The JSON data and Markdown descriptions fetched at build time are embedded into the resulting HTML, so the browser never fetches them a second time.
  • Multiple languages — the language is part of the URL (/pl, /en), and each language version has its own prerendered HTML. Interface texts come from translation files (@ngx-translate), and the list of available languages from the site configuration. On the root address the language is taken from the visitor's earlier choice, and when there is none, from the browser preferences. The switcher in the navigation bar changes the language without a reload and without leaving the current page.
  • SEO — every page carries a canonical link and a full set of hreflang links to its language versions (together with x-default), and the lang attribute on <html> matches the content being shown. The CV download page is excluded from indexing.
  • Light and dark mode — theming based on CSS variables, toggled by a class on the body element, with smooth color transitions. The choice is remembered and applied before the page is first painted, so there is no flash of the other theme on load.
  • Media gallery — support for images and video. The file type is detected from the extension, thumbnails scroll horizontally, and the preview opens in a lightbox. GLightbox is loaded only when the first gallery appears, as a single shared instance for the whole page.
  • Thumbnail generator — the tools/generate-thumbnails.py script (Pillow, OpenCV) creates optimized JPEG thumbnails for images and from the first frame of videos, following the thumb_<name>.jpg naming convention.
  • Collapsible descriptions — long project descriptions are shortened by default, with a fade gradient and a "Show more" button. The button appears only when the content exceeds a given height, and the height is recalculated on window resize and after a language change.
  • Technology badges — each technology can be assigned a color in technologies.json, and labels without an entry get a default color.
  • Scroll animations — sections and cards appear in a cascade, triggered by an IntersectionObserver; each element animates only once.
  • Responsive layout — based on the Bootstrap 5 grid, with a collapsible menu on mobile devices.
M2Chat
WIP
(2025)
Source code
C#
.NET MAUI
Blazor Hybrid
MudBlazor
Bootstrap
ASP.NET Core
EF Core
FluentValidation
PostgreSQL

A desktop application for monitoring the in-game chat in Metin2. Chat is read purely from the screen: the game window is captured every 100 ms and the text is recognized by my own pixel-based OCR algorithm. The application doesn't interfere with the game process, meaning no client files are modified, no memory is read and no code is injected.

The client is a Blazor Hybrid app (.NET MAUI, .NET 10) with a MudBlazor UI. Screen capture is implemented for Windows (P/Invoke into user32/gdi32: GetDC + BitBlt over the client area of the selected window). The backend is an ASP.NET Core Web API with a PostgreSQL database.


Program Features
  • Game window selection (any window picked by the user) and continuous chat reading
  • Separation of player messages and system messages (recognized by text color)
  • Grouping duplicates into a list of unique messages with first-seen / last-seen timestamps
  • Pulling item links ([name]) out as separate objects within a message
  • Boss tracker: remaining count per channel plus a countdown to the next respawn
  • Boss kill feed (time, channel, player, boss)
  • Boss kill rankings per player, with a configurable time window
  • Chat activity statistics (unique messages per minute)
  • Per-server configurations that can be shared with other users
Character Recognition

Instead of an off-the-shelf OCR engine I used bitwise pattern matching, which fits the game's fixed raster font well:

  • Charset build-up: the font bitmap (charset.bmp plus the character list in charset.txt) is scanned column by column. Every glyph is encoded as a UInt128 mask and stored in a two-way dictionary (mask <-> character).
  • Line reading: for each of the 20 chat lines (bottom to top) consecutive pixel columns are scanned. A pixel in one of the colors defined in the configuration sets a bit in a UInt128 buffer (|= with a bit shift), and an empty column ends the glyph and triggers a dictionary lookup.
  • Glued glyphs: when the full mask has no match, the buffer is progressively trimmed with a bit mask starting from the longest variant, the matched fragment is removed with a bit shift, and the remainder carries over to the next iteration.
  • Spaces are detected from runs of consecutive empty columns.
  • Unknown glyphs are written to a log together with the bitmap fragment, which makes extending the charset easy.
  • Character color carries information: it separates system messages from player messages and tells the item type in a link apart (equipment / other).
Game Server Configurations

Private servers differ in font, chat colors, message format, channel count, server start hour and boss list (name, count, respawn time).

  • Message patterns are stored with {Channel}, {Player} and {Boss} placeholders and compiled into regular expressions with named groups, from which the kill details are pulled out.
  • Respawn times are calculated from the server start hour and each boss's interval. The tracker counter resets automatically 3 minutes before a respawn (guarded against double resets).
Web API Service

ASP.NET Core Minimal API (.NET 10), EF Core + PostgreSQL, validation via FluentValidation.

  • User authentication using ASP.NET Core Identity (cookie-based authentication).
  • Configuration versioning: every save creates a new version (number, JSON payload, hash) linked to its parent entry.
  • A user can publish a configuration and others can import it (imports are counted).
  • An import remembers the version number. Once the author publishes an update, importers are told a newer version is available and can sync it at any time.
  • The author can change the visibility or delete the configuration (soft-delete). Users who imported it earlier keep their access.
Planned Features
  • Message filtering
  • Notifications for chat events
  • Muting players
  • Reading item bonuses from chat
  • Extended statistics panel
  • Chat streaming
Calyx Engine
(2024)
C++
OpenGL
GLM
ImGui
EnTT
Box2D
GameNetworkingSockets
Premake5

A 2D game engine written in C++ with a graphical editor: an OpenGL renderer, an Entity Component System (EnTT), physics simulation (Box2D), native scripting and a custom networking module built on GameNetworkingSockets.

The core architecture is based on the open-source version of the Hazel engine, but I made a lot of changes and added my own solutions. The biggest one is the networking module, which I wrote entirely myself. It's a hobby project at an early stage, not meant for commercial game production; development is currently on hold.

The networking module was built for my master's thesis. In it I studied how network latency affects the quality of the gameplay simulation, and more specifically: how different parameter values of the client-side prediction algorithm translate into the frequency and scale of desynchronization.

The project is configured with Premake5 (Windows, Visual Studio) and builds in three configurations: Debug, Release (with the editor) and Distribution, an editor-free build that serves as the game runtime.

The engine was previously developed under the name Proton2D. The whole git history lives in the old repository https://github.com/Damian-Zuk/proton2d.


Editor (ImGui)
  • Viewport, scene hierarchy, object inspector, content browser (textures, scenes, prefabs), settings panel and statistics panel.
  • Simulation control: start / pause / stop, with a snapshot of the scene state that is restored once the simulation stops.
  • Multiple game instances in a single process, each with its own viewport, scene manager and network manager. So you can run a server and a few clients side by side and test multiplayer gameplay without leaving the editor (a client instance can start automatically once the server goes up).
2D Renderer (OpenGL / Glad)
  • Batch renderer: up to 10,000 quads per batch by default, texture slot count read from the driver (up to 32), separate pipelines for quads, circles, lines and text.
  • Sprites and texture atlases (TextureAtlas, Sprite), 9-slice scaling and atlas-based animations.
  • Text rendering from MSDF atlases (msdf-atlas-gen), an orthographic camera with zoom, framebuffers and a draw call counter.
Scene, ECS and Assets
  • Scene wraps the EnTT registry. Entity is a pair of an identifier and a scene pointer, and every object gets a UUID.
  • The parent-child hierarchy is done with a relationship component (pointers to the first child and to siblings), with local and world positions kept separate.
  • Scenes and prefabs are serialized to JSON. A prefab is an object template shared across scenes, also used when spawning objects over the network.
  • Physics runs on a fixed timestep (accumulator), independent of the frame rate.
Native C++ Scripting
  • A class hierarchy AppScript > GameScript > EntityScript (application, game mode and single object logic), a factory that creates scripts by class name and a registration macro.
  • Script fields registered with the REGISTER_FIELD macro show up in the inspector and are serialized together with the scene. Supported types are scalars, vectors, strings and any trivially copyable data.
  • A field marked with REPLICATED_FIELD is synchronized over the network automatically, optionally with a callback invoked when a new value arrives.
  • There is no hot-reloading; I was planning a C# scripting engine down the line.
Physics (Box2D)

Rigid bodies, box and circle colliders with material parameters and collision filters, sensors, contact listening with a callback into the script, and revolute joints (attaching an object to its parent).

Networking Module

A client-server architecture with a fully authoritative server, running as a listen server, a dedicated server or a client. All communication goes through a custom binary stream (the header is written back once the payload size is known), and every value read is checked against the remaining buffer size. An inconsistent packet disconnects the client instead of reading out of bounds.

  • Protocol: the handshake carries both the engine and the game protocol version, and a mismatch in either closes the connection with a specific error code. Message types cover object spawn and despawn, replication, sequence number acknowledgements and custom messages sent from scripts (there is no RPC).
  • Delta replication: the server remembers the last sent state separately for each client and sends only what changed. Transform components are marked with bit flags (PositionX, PositionY, ScaleX, ScaleY, Rotation), and changes to script fields are detected with a CRC32 checksum computed per client. An object with nothing to report is rolled back out of the stream.
  • Cull distance: objects beyond a given radius from the player are not replicated, and their physics bodies are put to sleep on the client.
  • Late joining: the server keeps a record of every spawn and despawn, so a new client receives the full scene state along with a forced full replication. An object is sent as a prefab identifier, or as a complete JSON description when it isn't a prefab.
  • Replication tickrate (64 Hz by default) is independent of the frame rate and can optionally be synchronized with the physics tick.
Transform Synchronization

The synchronization method is set per object in the inspector, together with the reconciliation and teleport thresholds and the correction duration:

  • Interpolation: movement is replayed between the two most recent server states.
  • Extrapolation: the position is predicted from a velocity that blends the local Box2D body velocity with the velocity implied by the last two server states, pushed forward by the measured lag.
  • Client-side prediction (method description): instead of replaying an input queue, the client buffers per-tick transform deltas together with a sequence number and sends that number to the server. The server echoes the last processed number back in the replication message, and the client discards the acknowledged deltas and applies the remaining ones on top of the server state. That gives the predicted position.
  • Reconciliation: the error correction is spread over time (interpolation toward the predicted state, compensating for deltas that build up during the correction) rather than snapping. Only crossing the teleport threshold moves the object instantly.
  • Dynamic extrapolation: my own idea. An object synchronized by interpolation switches to extrapolation the moment it makes physical contact with a predicted (locally controlled) object, and goes back to interpolation once its divergence from the server state drops below a threshold. This limits the visible drift of objects colliding with the player character.
Diagnostic and Research Tools
  • Network latency simulation set from an editor panel (artificial lag on both incoming and outgoing packets).
  • Network statistics logged to a file: ping, throughput in both directions and the average number of replicated objects per message, sampled at fixed intervals, with a header describing the scene and the tickrate. This data was used for the measurements in the thesis.
  • In-editor statistics: object counts (including scripted and networked ones), frame time, and per-client ping and throughput.
  • Assertion macros, an instrumentation profiler (writing a trace file) and a logger (spdlog).
CVE Report Generator
(2024)
Python
HTML
JavaScript
CSS
NVDLib
JSON
Nmap XML

A Python CLI tool that generates a vulnerability (CVE) report for IoT devices. It takes a manually prepared device list in JSON format (model, firmware version, MAC address) and/or network scans made with Nmap (XML). For every identified piece of software, that is the firmware, services on open ports and the operating system, the script queries the NIST NVD database and attaches the vulnerabilities it finds to the output report.

Built for the "Security of IoT Systems" course at Rzeszów University of Technology.


How It Works
  1. Load the device list (devices.json). Manually entered data forms the base of the report and takes precedence over scan data.
  2. Parse the Nmap scans (XML, nmap -A -oX). Hosts are matched to listed devices by MAC address, and unknown ones go into the report as new entries. From a scan the tool pulls the IPv4 address, the vendor (from the MAC), ports in the open state together with their services, and the OS detection result.
  3. Resolve CPE identifiers. For items without a CPE, a keyword search is performed (model + version, product + version, OS name + family + version). Identifiers in the older CPE 2.2 format are converted to CPE 2.3.
  4. Fetch vulnerabilities with cpeName queries. If no CPE could be resolved for a device, a keyword search is used instead and the CPE is filled in from the first matching vulnerability.
  5. Save the report to a JSON file with the same structure as the input, extended with a vulnerabilities section.
What the Report Contains

The report is an array of devices, and each entry contains:

  • Device identification: model, description, MAC address, IPv4 address, vendor, firmware version and the assigned CPE. Custom fields from the input file are carried over into the report and shown by the report viewer.
  • Open ports and services: port number, service name, product, version and CPE.
  • The operating system: name, whether the match came from Nmap along with its accuracy percentage, and a list of OS classes (vendor, family, version, accuracy, CPE).
  • Vulnerabilities in three categories: firmware, service (keyed by product version) and os (keyed by family version). Each category has the total number of CVEs found and their list, where a single entry carries the CVE identifier, publication date, description, the CVSS metric used, the numeric score and the severity level (LOW / MEDIUM / HIGH / CRITICAL).
Configuration (config.ini)
  • Paths: the output file, the device list, and the directory or single file with the Nmap scans.
  • A NIST NVD API key, which shortens the delay between requests from 6 s to 0.6 s.
  • A list of operating systems skipped when querying the database (e.g. linux, android). For those the number of hits is so large that the result wouldn't be useful anyway, so the report only records a note about the skipped check.
Report Viewer

A static report-viewer.html page, no backend and no dependencies. The JSON file is loaded via drag and drop. Devices are shown as collapsible panels, severity levels have their own colors, and CVE and CPE identifiers link to the corresponding NVD pages. The same view also handles the input device list, since it has the exact same structure.

Limitations
  • CVE matching relies on an exact CPE. Version ranges of vulnerable software (versionEndIncluding) are not analyzed, so a vulnerability described by a range may go undetected.
  • The accuracy of the keyword search depends on how the vendor names its products in the NVD database.
Ansible Project
(2024)
Ansible
Ansible Semaphore
Ubuntu
AWS
PHP
MySQL
Apache

A student project in which Ansible automates the configuration of virtual machines, installation of system packages and deployment of a simple LAMP application.

Everything runs in the AWS cloud: the control node and the managed web servers sit on separate instances. Playbooks are executed through Ansible Semaphore, a graphical interface for Ansible. It lets you run tasks from the browser, set up schedules, keep access keys in one place and browse the run history together with logs.

A big part of the project was documenting the whole setup: from preparing the instances and SSH access, through installing and configuring Semaphore, to describing how each role works. The repository contains Ansible files only: playbooks, roles and the inventory.


Ansible Roles
  • create_admin_user – managing administrator accounts.
    • Creates users based on the admin list (with a set UID and description).
    • Configures SSH access by adding public keys to authorized_keys.
    • Grants sudo rights with an entry in /etc/sudoers.
  • update_system – operating system updates.
    • Refreshes the APT package cache.
    • Upgrades all installed packages to the latest available versions.
  • setup_apache_and_php – installing and configuring the web environment.
    • Installs the Apache HTTP server.
    • Installs PHP together with the required modules.
    • Enables the PHP module in Apache.
    • Raises memory_limit in the PHP configuration to 256 MB.
    • Restarts Apache so the changes take effect.
  • setup_mysql_db – installing and configuring the MySQL database server.
    • Installs the required dependencies (pip, PyMySQL).
    • Installs and starts the MySQL server.
    • Creates a MySQL user with full privileges.
    • Creates the application database.
    • Creates the posts table if it doesn't exist yet.
    • Inserts sample data into it based on an SQL template.
  • deploy_website – deploying the web application together with its configuration.
    • Pulls the latest version of the application from a Git repository.
    • Syncs the code to /var/www, replacing files only when changes are detected.
    • Creates the application's configuration directory and loads the database variables.
    • Generates config.php from a Jinja2 template, filling in the database credentials and site parameters.
Poll Wizard
(2023)
Python
FastAPI
SQLAlchemy
Pydantic
PostgreSQL
Redis
TypeScript
React
Bootstrap
Docker

A web application for creating simple single-question polls and voting on them, modeled after the StrawPoll.com platform.

The backend is a REST API built on FastAPI (SQLAlchemy ORM, PostgreSQL, validation through Pydantic), with Redis holding the session state and the rate limit counters. The frontend is a single-page application in React + TypeScript (Vite, Bootstrap). Everything runs through Docker Compose, and the API documentation is generated automatically (Swagger).


Application Features
  • User accounts: registration with reCAPTCHA, login and authentication with JWT tokens.
  • Poll creation: a question and 2 to 16 unique answers (validated on the API side).
  • Voting: one vote per poll for a logged-in user, with instant results (vote counts and percentage shares on bars).
  • A user profile with a list of the user's own polls; the author can delete their poll.
  • Rate limiting: request limits counted in Redis, separately for whole endpoint groups and for sensitive operations (e.g. 3 sign-ups per minute, 5 new polls per minute).
A Custom JWT Session Module Backed by Redis

Instead of simply issuing tokens, I wrote a separate session management module (fastapi_jwt_redis) that plugs into the FastAPI dependency system:

  • A token pair: a short-lived access token and a refresh token, each with its own jti identifier, expiration time and not-before time.
  • Refresh token rotation: every session refresh issues a new token pair, and the old refresh token is marked as used in Redis.
  • Reuse detection: an attempt to use a refresh token a second time (a typical sign that the token was stolen) is treated as a breach. The request is rejected and the whole session is invalidated.
  • Cascading revocation: Redis stores the links between tokens from successive rotations, so a logout or a detected abuse blacklists the whole chain of tokens belonging to that session, not just the token presented in the request.
  • Verification at the entry point: a custom security class checks the header scheme, the signature, the expiration time, the token type match (a refresh token won't pass where an access token is required) and whether the identifier is on the blacklist.
  • Redis entries live as long as the refresh token, so the store cleans itself up.
Security and Validation
  • Passwords hashed with Argon2, with complexity requirements (length, upper and lower case, a digit, a special character) checked at registration.
  • Input validation through Pydantic schemas: length limits on the question and the answers, a uniqueness requirement for answers, username format checks, and verification that the email and username aren't already taken.
  • Resource-level authorization: only the author can delete a poll, and a repeat vote on the same poll is rejected on the API side.
  • Sensitive parameters (JWT secret, token lifetimes, database credentials) are configured only through environment variables, and development endpoints are registered only in debug mode.
Frontend

A React application with client-side routing and background session refresh (the access token is exchanged before it expires). A single poll has its own address, so it can be shared, and the votes already cast by the logged-in user are fetched with one batched request covering every poll on screen.

What's Missing
  • pagination,
  • GUIDs for poll links instead of IDs,
  • private polls (accessible via link only),
  • voting without an account,
  • a vote limit per IP address.
Auto Parts
(2021)
Python
Django
PostgreSQL
Bootstrap
JavaScript
Leaflet
Heroku
PWA
Agile Scrum

An online store selling car parts. We built it in a team of five, working in Scrum together with a group from the management faculty, as part of the "Software and Database Engineering" university course.

The store is built with Django (Python) on a PostgreSQL database, with server-side templates and a Bootstrap interface. The application is prepared for the Polish market, works as a PWA and was deployed to Heroku (gunicorn, whitenoise).

My part of the work was the product catalog and the presentation layer: filtering and sorting of the product list, splitting the assortment into sections and categories together with the navigation bar, pagination, discounted prices with a sale view and a special offers section, and moving the home view queries into a separate service layer. On the interface side I prepared, among other things, the responsive navigation bar, the floating cart and the templates for the product page, product list, login, registration and order summary.


Product Catalog
  • Categories are hierarchical: a category can have a parent, browsing a section shows products from all of its subcategories, and the navigation bar switches to the subcategories of the selected section.
  • Filtering and sorting (django-filter) by name, price and discount, with pagination at 12 items per page. Price sorting takes the effective price into account: an SQL expression picks the discounted price if there is one, and the base price otherwise.
  • The search covers product names and descriptions.
  • Discounts get a separate sale view and a special offers section on the home page. Each discounted product shows the price before and after the reduction and the amount saved.
  • Product parameters are kept in a JSON field, so different part categories can be described with different attribute sets without changing the database schema.
Product Comparison

Based on an open-source Django module (the comparison list lives in the session, items are added and removed over AJAX), which I extended with a parameter table built from the JSON field: the attributes of all compared products are merged into one set and laid out row by row, with a dash for missing values. Products can be compared within one category.

Store Locations and Geolocation
  • Sales points have an address, phone number, photo and geographic coordinates. They can be searched by city, address, postal code and phone number.
  • Nearest store lookup: the browser gets the user's position (Geolocation API) and the backend finds the closest point with an SQL query using the haversine formula, with no need for GeoDjango. The result comes back as JSON and lands on an OpenStreetMap map (Leaflet) next to the marker of the user's position.
  • Stock levels are tracked separately for each location (product quantity at a given point).
Accounts and Orders
  • Registration with email activation: the account stays inactive until the user clicks a link with a one-time token. The username and password are checked by custom validators (minimum length, a digit, an uppercase letter) with messages in Polish.
  • Two user types: individual customer and repair workshop. The account type changes which payment methods are available.
  • The cart is simply an order in the "unpaid" state, with adding items, decreasing quantities and removing lines, and totals recalculated with discounts applied.
  • Checkout is a delivery address form with a country choice and a payment method (BLIK, card, Apple Pay, Google Pay, PayPal).
  • When paying by invoice (available to workshops), a PDF invoice is generated with the order lines and the VAT rate, stored with the order and available from the purchase history.
  • The user profile allows editing contact details and browsing placed orders.
Other Details
  • Product images, store photos and invoice files are kept in the database (separate tables for content, filename and MIME type) instead of on disk. That's convenient on a platform that doesn't keep files between restarts.
  • The Django admin panel manages the catalog, locations and orders, with Polish model names.
  • A management command that generates test data (categories, a few hundred products, locations) for development.
Push The Box
(2020)
C++
SFML
CMake

A puzzle game modeled on the classic Sokoban: the goal is to push every crate onto its designated target tile. Written in C++17 with the SFML library and no ready-made engine. The game loop, state system, entities, animations and UI controls I implemented from scratch. The in-game interface is in Polish.

The project is built with CMake and targets the Win32 (x86) platform, matching the bundled SFML version.


Application Framework
  • A game loop with frame time measurement (delta time) and an FPS cap. Window configuration and the asset list are loaded at startup.
  • A state stack: the main menu, options, level selection, gameplay and editor are separate states. Only the state on top of the stack is updated and rendered, and closing it releases every entity it owns.
  • Entity layers: membership in up to 32 layers is stored as a bit mask, which makes it possible to show and hide whole groups of elements within a single state (e.g. in the editor menu: main view / new level / load).
  • Entities have a parent-child hierarchy, position pinning to another entity, spritesheet animation and smooth movement toward a target point.
  • The layout is resolution-independent: positions and sizes are expressed in coordinates normalized against a 1920×1080 model and scaled to the actual window size.
  • The asset manager loads textures and fonts based on an external asset list file (file name, identifier, smoothing).
  • Custom UI controls: button, checkbox, dropdown list, text box (character limit, digits-only mode, blinking cursor) and text label.
Gameplay
  • Movement in four directions with character animation. Pushing a crate only succeeds when the tile behind it is free, and collisions are checked against a grid of tile identifiers rather than against sprites.
  • Move undo: a register of the last 10 moves (direction, animation and the pushed crate if any) working as a FIFO buffer. An undo replays the move for both the player and the crate and updates the collision grid.
  • Move and time counters, level restart, and control via keyboard or on-screen buttons.
  • The camera follows the player with a margin from the edge of the view and locks an axis whenever the board fits entirely on screen.
  • A level ends the moment every target tile is occupied. The result is written to the save file only when it beats the previous one (fewer moves or a shorter time).
  • 16 built-in levels and a separate menu for custom levels.
Binary Level Format

The header (18 bytes) holds the board dimensions, the player start position and the crate count. It is followed by an array of target tile positions (8 bytes each) and the tile grid stored as one byte per field (no tile / floor / wall / crate).

The floor and wall texture variant is drawn from a seed calculated from the level dimensions. This way the board looks varied, yet renders identically on every load.

Level Editor
  • Create a new level (name, dimensions from 5 to 99 per axis) or load an existing one for further editing.
  • Tools for placing walls, floor, target tiles and the player start point, plus an eraser and a crate count control. A board larger than the view is scrolled with the arrow keys.
  • Validation on save checks that the start point is set, that crates and target tiles are present, and that their counts match. The message lists every detected problem at once.
  • Saved levels land in the custom levels directory and are immediately available from the game menu.
Progress and Settings Persistence
  • Progress is stored in a binary file with one record per level (completion, best time, lowest move count), preceded by a SHA-256 checksum computed from the data and a fixed string. A checksum mismatch means the file was hand-edited or corrupted and ends with the progress being reset.
  • Display settings (five predefined resolutions, fullscreen mode) are written to a configuration file. Applying changes recreates the window and recalculates the interface layout without restarting the game.
Console Chat
(2019)
C++
Winsock
Diffie-Hellman Key Exchange
SHA256

A console client-server chat for Windows, written in C++ on Winsock sockets (TCP), with no external libraries. The server and the client are two separate command-line applications. All the logic (accounts, ranks, permissions, bans) sits on the server side, and the client only displays output and forwards whatever the user types.

One of my early hobby projects. I wrote it to learn network programming and multithreading.


Architecture and Protocol
  • A thread per client: the main thread accepts connections, and each authentication and session handler gets its own thread. Access to the server files is guarded by a mutex.
  • A custom packet protocol: every packet is preceded by its type (message, command, input request), and the packet structure is picked by a template based on the type of the object being sent.
  • Server-driven input requests: the server can ask the client to prompt for text, passing the field label and whether the characters should be masked. Password login, account creation and operation confirmations all work through this mechanism, so the client doesn't need to know any of those flows.
  • Public or local mode chosen at startup, together with the port, the slot count and an optional server password. On the first run the server creates its file directory and a message of the day (MOTD).
Encryption and Authentication
  • The client and the server agree on a shared key through a Diffie-Hellman key exchange (modular exponentiation over a fixed base and modulus). The agreed secret combined with the username goes through SHA-256, and the actual session key is taken from that hash.
  • Every packet in both directions is encrypted with a byte stream generated by srand seeded with successive characters of the key and mixed into the data with XOR.
  • Account passwords are never stored in the clear. The server file holds their SHA-256 hashes (the algorithm implementation is part of the project).
  • The cipher itself is a custom construction of low cryptographic strength. The point was to understand how key exchange works, not to actually protect the transmission.
Accounts, Ranks and Moderation
  • Ranks: GUEST (unregistered), USER, VIP (colors in messages), MOD and ADMIN, compared numerically. A command requires a rank no lower than its threshold.
  • Registration is simply setting a password for your own nickname. On the next connections the server asks for it in a masked field.
  • User commands: the list of available commands, who is online, private messages with a quick reply to the last person you talked to, and disconnecting.
  • Moderator commands: details about online users, kicking someone off the server, IP bans and reviewing the ban list.
  • Admin commands: granting and removing ranks, removing a password from an account, reviewing accounts and ranks, plus shutting down or restarting the server with the option to cancel.
  • Timed and permanent bans: the duration is given in shorthand (e.g. 7d, 12h, 30m) and the expiry is stored as a timestamp. Once it passes the entry stops applying, and the remaining time is shown in a readable form.
  • Accounts, ranks and bans are kept in the server's text files, loaded at startup and updated as changes happen.
MTConsole, a Custom Multithreaded Console Library

A plain console doesn't work for a chat, because text arriving from the network breaks up the line the user is currently typing. For this project I wrote a separate component that solves that problem:

  • Input is handled on a separate thread, character by character, and the input line is drawn independently of the message stream. Before a new line is printed, the input field is cleared and redrawn below it, and printing itself is guarded by a mutex.
  • The cursor can be moved within the typed text (left/right arrows), characters can be inserted and deleted mid-line, and wrapping at the edge of the console window is handled correctly.
  • A history of entered commands is available through the up/down arrows.
  • Input can be masked with asterisks (for passwords), and the input field can be hidden and disabled entirely.
  • Text coloring works through {number} markers embedded in the content. The same string can be printed with colors or stripped of the markers, which is used for the messages of VIP-ranked users, among other things.
Flappy Bird Clone
(2018)
Python
Pygame

A Flappy Bird clone written in Python with the Pygame library. Pygame only handles the window, drawing and input events, while the game layer (objects, scenes, resources, update loop) I built from scratch.


Game Framework
  • The main loop measures the frame time and has an FPS cap. All movement is calculated in units per second and multiplied by the frame time, so the game behaves the same regardless of how fast the machine is.
  • A base game object (with an update method and an active flag) is extended by a drawable object (position, size, texture or fill color). The bird, pipes, background, texts and buttons all inherit from it.
  • The resource manager keeps central registries of updated and drawn objects plus a dictionary of resources available by name. The draw list is sorted by z_index, which sets the layer order.
  • The scene manager: a scene is a named group of objects toggled with a single call. The scenes are tap (start screen), playing, died and died_new_best, so changing the game state comes down to enabling one scene and disabling the others, instead of hiding individual elements by hand.
  • The graphics layer has global scale factors that every coordinate and size passes through, plus an AABB collision check.
  • The font manager caches fonts by name and size, and text labels regenerate their texture only when the content has actually changed.
  • A debug overlay shows the frame rate, frame time, process memory usage and the number of active pipes in the corner of the screen.
Pipe Generation and Recycling
  • The bird never moves horizontally. The game keeps a counter of the distance covered, and pipe positions are calculated from that value and the pipe index. This makes the world endless without the game having to store any of its history.
  • The number of pipe objects is fixed and matched to the screen width. Instead of creating and destroying pipes, the game slides a window of indices and reuses the same objects, giving them new positions and heights.
  • Gap generation is deterministic: the gap position for a given pipe number is calculated from a seed built out of the run's start time and the pipe index. The same pipe always gets the same gap, so its parameters can be reproduced at any moment without remembering earlier draws.
  • A point is awarded once the bird passes the halfway point of a pipe's width, and collisions are checked separately against the top and bottom sections.
Flight Handling

Falling speed builds up to a terminal value, and the acceleration depends on the phase of flight: an ascent is damped more strongly than a free fall, which produces the characteristic jump arc. After a collision the bird switches to a separate falling mode with higher acceleration and a higher terminal speed, stops only at ground level, and the end screen appears after a short delay.

High Score Persistence

The record is kept in a binary file preceded by a SHA-256 hash computed from the score and a fixed string embedded in the code. On load the hash is recomputed and compared against the stored one. If it doesn't match (someone edited the file by hand) or the file is corrupted, the record is discarded and the count starts over from zero.

2D Platformer
(2018)
Source code
C++
SDL2

A 2D platformer written in C++ with the SDL2 library. SDL2 only handles the window, texture drawing and keyboard input; the rest (game loop, objects, physics, camera and map loading) I wrote from scratch.


Engine Framework
  • The game loop measures how long each frame takes. All speeds are given in units per second and multiplied by that time, so movement doesn't depend on how fast the machine is.
  • A base game object (identifier, active flag, update method) is extended by a dynamic object with position, velocity, size and mass. The player and the map blocks both inherit from it, so the physics works on one shared type.
  • The engine keeps a central registry of objects with a pool of reused identifiers: the number of a deleted object goes back to the pool and the next created object gets it.
  • Objects can be looked up by type. A template returns a list of all objects of a given class, and both the physics and the rendering use it.
  • The world and the interface are described in a fixed 1920×1080 resolution; when drawing, the coordinates are converted through scale factors to the actual window size.
  • On top of that there is a custom 2D vector template with overloaded arithmetic operators and an input manager that polls the keyboard once per frame.
Map and Rendering
  • A level is loaded from a text file that stores the tile size, the board dimensions in tiles and a list of identifiers. A non-zero identifier becomes a collision block. The test map is 300×200 tiles of 32 pixels each, which gives a 9600×6400 pixel world.
  • Tiles are drawn from a texture atlas, and the identifier from the map file picks the atlas region.
  • Before drawing, each object is checked against the area seen by the camera. The rest of the board is skipped.
Physics and Collisions

Collisions don't work by pushing an object back once an overlap is detected. For each axis separately, the objects lying in the path of movement are checked and the nearest blocking edge in the direction of travel is found; the new position is clamped to it. This way an object can't skip through an obstacle even at very high speed, and the map borders stop movement on their own.

Gravity builds up with the object's mass until it reaches a set terminal velocity. Touching the ground or the ceiling zeroes the vertical speed and updates the "in air" flag, which controls jumping (you can only jump while standing on the ground).

Character Controls

Horizontal movement accelerates from a starting speed to a maximum one. In the air the acceleration is lower than on the ground and the top speed a bit higher, so running and steering mid-jump feel clearly different. Changing direction in flight slightly adjusts the vertical speed, and a separate key takes the player back to the spawn point.

Camera and Parallax Background
  • The camera follows the player with a dead zone: it starts moving only when the character leaves a set area of the frame. It is also clamped to the map borders, so it never shows anything outside the board.
  • During a fast fall the framing point smoothly moves up to show more space below the character, and once the fall slows down it just as smoothly returns to the default.
  • Each background layer has a set distance from the camera, and its scroll factor is calculated from that distance and the field of view, following the rules of perspective. There is no need to tune it by hand for every layer. Optionally the layer size is scaled from the distance as well.
  • Layers repeat horizontally without end, with rounding errors corrected where neighboring copies meet. The same texture used a few times at different distances builds the depth of the jungle.
Roulette Simulator
(2017)
HTML
CSS
JavaScript
Bootstrap
jQuery
SHA256

A roulette simulator inspired by the CSGODouble platform, running entirely in the browser: no backend, built on plain JavaScript with jQuery and Bootstrap. An early project from 2017, from the time I was learning the basics of JavaScript and web development.

The wheel has 15 fields, and a bet is placed on one of three colors:

  • red (1–7) – 2X payout,
  • black (8–14) – 2X payout,
  • green (0) – 14X payout.

Result Generation (Provably Fair)

The algorithm reproduces the "provably fair" scheme used by platforms of this kind: the outcome is fixed before the spin begins and can be verified afterwards. The winning number is calculated from three values:

  • seed – a secret string disclosed the following day, so the previous day's results can be checked (generated as the hash of a random 48–64 character string),
  • public – a public value, which here is the current date in year-month-day form,
  • round – the round number within that day.

The values are joined into a single string and passed through the SHA-256 hash function. The first eight characters of the result are read as a hexadecimal number, and its remainder modulo 15 picks the winning field. Further characters of the same hash also produce the offset within the field, so the pointer stops at a different spot of the same field every time, in an equally verifiable way.

Wheel Animation

The fields sit on a looped strip in alternating order (red and black on both sides of the zero), and a spin comes down to shifting the background by a calculated distance. The movement slows down exponentially: each step covers a fraction of the remaining distance, so the closer the wheel gets to its target, the more gently it slows. The starting distance is a few full revolutions plus the path to the drawn field, and the rate of deceleration is adjustable with a speed slider.

Gameplay
  • A countdown to the start of the round with a progress bar. Betting is locked while the wheel spins, and once the result is announced the round number increments automatically.
  • Bet limits: a minimum stake of 10 coins and at most 3 bets per round.
  • A stake field with shortcut buttons (+10, +100, +1000, +10000, half, double, all-in). The balance and the pools on each color change through an animated counter, green on a win and red on a loss.
  • Free coins to collect once the balance drops below a set threshold.
  • A history of the last ten results, sound effects for the spin and the stop, and event messages.
Settings Panel

A separate panel lets you check the generator yourself: manually set or regenerate the seed, public and round values, disable the automatic round increment, change the countdown length and the animation speed, lift the free coins limit, and start a spin immediately without waiting for the countdown to finish. By entering the same three values, any earlier spin can be reproduced to confirm the result wasn't changed along the way.

Damian Żuk — Software Engineer © 2026