carlos@cesaints: ~/projects/member-management.md — zsh

Command: cat projects/member-management.md

projects/member-management.md · 2.7 KB

A national confederation's member system, modernized with the legacy still live

A national professional confederation keeps its members' records, the digital membership card with a QR code, the regional offices' request batches and online membership with a contract and consent in a legacy PHP system that runs in production. As the sole engineer, I lead the modernization from the inside without stopping the system: version control, recorded decisions, tests from zero, CI with disposable databases and deploys with backup and automatic rollback.

open the demoa simulation of the system with fictional data · 6 screens

Role
Sole engineer of the modernization
Period
Jun 2026 – present
What I did

Me: The architecture and plan of the modernization, its 16 recorded decisions, the batch workflow as a state machine, the rewritten online membership, the finance committee's reports, the tests, CI/CD, the staging environment and the security method.

Other people: The original system already existed and ran in production before my work; the legacy codebase is not mine.

Private project, described without identifying the client, product or company.

Diagram

The legacy system stays live, with new code growing inside it

Members, regional offices and the national office, the public QR check and membership applicants all come in through the same routes, each declaring its profiles and closed by default. Behind them, the PHP app holds the legacy code in production and the new layered code growing from the inside (strangler fig). The data lives in separate MySQL databases, the main registry and online membership, checked by a reconciler. Delivery goes through GitHub Actions: pre-checks on disposable databases and a deploy with backup, health check and automatic rollback.

Context

A national confederation of professionals keeps its members’ records and the standing of each professional registration in a web system. The state representations, the regional offices, send card requests in batches; the national office checks them, bills, prints and passes each regional office its share. Anyone can check through the card’s QR code whether a professional is in good standing. The system is old, runs in production and holds real personal data.

What I built

  • Version control from the server. The first commit is a snapshot of what was live, code only, with no secrets and no user data. Both databases became versioned as a schema plus migrations, with secret detection in CI.
  • The batch workflow as a state machine. In preparation, under review, awaiting payment, in production, final review and done, with deadlines in business days that skip weekends and national and state holidays, extensions with a reason, two-step pending issues (the regional office flags them resolved, the national office confirms) and acknowledgement recorded on both sides.
  • Card printing tied to good standing. It only prints when every registration in the batch is in good standing. The screen disables the button and explains what is missing, and the generator repeats the check on the server.
  • Online membership, rewritten. A pre-registration with documents, an electronic contract and consent under LGPD (Brazil’s data protection law) recorded separately; routing by state decided on the server; review document by document; and the member only exists after payment.
  • Reports for the board. A year-over-year comparison with two readings, finances by state and by batch, and an in-house XLSX export in the format of the office’s spreadsheet.
  • Delivery with a safety net. CI pre-checks with disposable databases and migrations applied twice to prove idempotency; deploys with backup, health check and automatic rollback; a staging environment with outgoing e-mail blocked, validated by the board before changes go live.

Security as a method

Code review, dynamic testing against a local container, security questions reviewed on every diff and authenticated red-team suites in CI. A written rule: no scanner against production. The categories covered follow the OWASP Top 10: A01 broken access control, A02 cryptographic failures and credential storage, A03 injection and XSS, A04 insecure upload design, A05 security misconfiguration and A07 authentication and session management. Specific findings are not published.

What is not here

The organization’s name, its professional field, real numbers and any detail that identifies the system. The demo uses a fictional confederation, with made-up people, registrations and amounts.

Constraints

  • A production system with members' personal data: what people see does not change without a decision.
  • One person, part time.
  • At the starting point, no automated tests, no staging environment and no usable version control.
  • Security testing only in a local environment; no scanner ever pointed at production.

Decisions

Modernize from the inside, without a rewrite

Context
Over 100k lines of PHP in production, no tests, and one person working part time. A rewrite would take years; upgrading everything at once would break screens with nothing to warn about it.
Choice
Strangler fig: the legacy keeps serving while every file touched leaves sanitized, new code is born in layers, and the language upgrade waits for a catalog of breakages built in a parallel container.
Gains
  • The system never stopped, and every improvement is small and reversible.
  • The rewritten module became the pattern for all new code.
Costs
  • Two code styles coexist for a long time, and the language upgrade is recorded as debt.

Authorization declared on the route, closed by default

Context
Access checks were scattered across screens; one omission would be enough to open a screen to a profile that should not see it.
Choice
Every route declares which profiles reach it (member, regional office, national office, forms or public), and a route without a declaration is refused. The regional office's scope comes from the session, never from the URL.
Gains
  • The permissions of 214 routes live in one place, easy to review.
  • Authorization tests per profile and per state run against that table.
Costs
  • A wrongly declared level only shows up when someone uses the menu, which called for navigation tests with a real session.

A member only exists after payment

Context
Someone who applies online is not a member yet: the documents still need review and the fee still needs paying. And the data lives in two databases with no foreign key between them.
Choice
The applicant lives in a pre-registration with an area of their own. Recording the payment creates, in one transaction, the person, the registration with the next number and the card. Settling and creating are separate, repeatable steps, with an idempotent lock and a reconciler between the databases instead of a distributed transaction.
Gains
  • No registration number for anyone who has not paid.
  • A failure while creating the member can be retried without settling the payment again.
Costs
  • One more state to explain to applicants, and a reconciler to operate.

Deploys and migrations with a safety net

Context
Publishing meant copying files to the server with no easy way back, and changing the database was an unchecked manual step.
Choice
Incremental deploys with a backup of what gets replaced, a health check and automatic rollback; the deploy pauses when a migration is pending, and the schema ships before the code, compatible with the previous version.
Gains
  • A bad release rolls itself back to the previous version.
  • A migration never runs without a backup and a pre-check.
Costs
  • One manual step per migration, and orphaned files on the server until an evidence-based cleanup.

A report that does not mislead decision-makers

Context
Comparing the current, still open year with closed years distorted the variation several times over.
Choice
The comparison always shows two readings, same period and calendar year, with the cut anchored on the last day with entries and the cut date written in the notice, the indicators and the spreadsheet.
Gains
  • The finance committee compares equal stretches of the calendar.
Costs
  • Two numbers for the same year, which the screen has to explain.

Stack and why

PHP with an in-house MVC
The legacy stays in production; new code lives in service layers inside the same app.
MySQL across two databases
Main records and online membership, linked in the application and checked by a reconciler.
PDF and QR generation
Card, contract and receipts with a QR code for public validation.
Docker
A reproducible local environment and the only target of security testing.
GitHub Actions
Pre-checks with disposable databases, deploys with rollback and migrations with backup.
CSS design system
Tokens with annotated AA contrast and automated contrast and usage checks.

Results

  • From zero to 47 automated test files, about 14.3k lines.

    audited private repositoryCount on the repository's working branch(Audit of the private repository, Sep 2026)
  • 214 routes, each with its access profiles declared on the route itself.

    audited private repositoryCount in the working branch's route table(Audit of the private repository, Sep 2026)
  • 16 architecture decisions recorded as ADRs, with options, pros and cons and a reassessment trigger.

    audited private repositoryThe project's decision log(Audit of the private repository, Sep 2026)
  • 42 versioned migrations: 27 in the main database and 15 in the online membership one.

    audited private repositoryRepository count(Audit of the private repository, Sep 2026)
  • Starting point: over 100k lines of PHP, no automated tests and no staging environment.

    self-reportedThe project's diagnostic note

Security angle

Attack surface

  • The member area, with personal data, documents and contracts.
  • Public lookup and QR validation of cards, seals and contracts.
  • Document uploads in online membership.
  • The regional and national panels, where the batches' money flows.

Controls in place

  • Authorization declared on every route and closed by default; the regional office's scope comes from the session.
  • Membership documents stored outside the public folder, checked by their real content and hashed with SHA-256.
  • Contract PDFs with a SHA-256 integrity code, and protocol numbers with a random suffix against enumeration.
  • Notifications that carry only a first name and a protocol number, never a tax ID, e-mail or address.
  • Authenticated red-team suites in CI that refuse to run outside the local environment.

What I would test today

  • A regional office trying to read or change members from another state.
  • Skipping steps of membership or of a batch with forged requests.
  • Enumerating protocol numbers on the public contract validation.

Evidence

  • audited private repositoryAudited private repository(Audit of the private repository, Sep 2026)