<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Veli Kaan Çetinel</title><description>Blog posts and projects from Veli Kaan Çetinel</description><link>https://kaancetinel.dev/</link><item><title>Identity space wars: when two systems disagree about what things are</title><link>https://kaancetinel.dev/blog/identity-space-wars/</link><guid isPermaLink="true">https://kaancetinel.dev/blog/identity-space-wars/</guid><description>Everyone assumed the new file id and the legacy reference were the same number. We sampled 55 production files — 54 of them differed, and the ranges overlapped. This is the story of the eight-phase fix.</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Of everything that happened during the &lt;a href=&quot;/blog/zero-downtime-migration&quot;&gt;ARES zero-downtime migration&lt;/a&gt;, one discovery changed how I think about system boundaries — and it started with a sentence everyone believed:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&quot;The v2 file id and the legacy reference number are basically the same thing.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;The assumption&lt;/h2&gt;
&lt;p&gt;Running two schemas side by side means every entity exists twice: once in the decade-old legacy tables, once in the new normalized schema. To keep clients working during the transition, APIs accepted and returned identifiers — and the comfortable belief was that for files, the new &lt;code&gt;files.id&lt;/code&gt; and the legacy reference (&lt;code&gt;islem_ref&lt;/code&gt;) were interchangeable. Same records, same numbers, no translation needed.&lt;/p&gt;
&lt;p&gt;It&apos;s the kind of assumption that feels safe precisely because everything &lt;em&gt;seems&lt;/em&gt; to work. Most traffic hits recent records, recent records were created in both systems near-simultaneously, and near-simultaneous creation produces ids that are... close. Close enough to pass every casual test.&lt;/p&gt;
&lt;h2&gt;The measurement&lt;/h2&gt;
&lt;p&gt;Before building more on top of that belief, we did the boring thing: pulled 55 production files and compared their ids across both systems.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;54 of the 55 differed.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Worse than differing — the two id ranges &lt;strong&gt;overlapped&lt;/strong&gt;. A given number could be a perfectly valid file id in &lt;em&gt;both&lt;/em&gt; systems, pointing at &lt;em&gt;two different files&lt;/em&gt;. That&apos;s not an error you catch with a &quot;not found&quot; response. That&apos;s an API call that succeeds, returns a real file — the wrong one — and corrupts data downstream with complete confidence.&lt;/p&gt;
&lt;p&gt;One overlap away from a claims adjuster editing someone else&apos;s damage file. We stopped everything id-related that day.&lt;/p&gt;
&lt;h2&gt;Two-front fix&lt;/h2&gt;
&lt;h3&gt;Front one: unify the file id space&lt;/h3&gt;
&lt;p&gt;For files themselves, we ended the dual identity at the schema level: the legacy reference was &lt;strong&gt;adopted as the canonical v2 file id&lt;/strong&gt; — one number, one meaning, in both worlds. That decision rippled through 13 file foreign keys and another 22 relation and audit FKs, but it removed the single most dangerous ambiguity: from then on, &quot;file 84213&quot; meant the same record no matter who said it.&lt;/p&gt;
&lt;h3&gt;Front two: the wire contract&lt;/h3&gt;
&lt;p&gt;Files were only the loudest case. Every reference vocabulary the APIs spoke — vehicle makes and models, provinces and districts, occupations, policy types, insurance branches, document types, campaigns, invoices, regions — had its own legacy id space, and clients had legacy ids baked into requests, caches and saved filters.&lt;/p&gt;
&lt;p&gt;You can&apos;t flip all of that at once. So we ran an &lt;strong&gt;eight-phase wire contract program&lt;/strong&gt;, one vocabulary family at a time:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ship the v2 ids alongside the legacy ones.&lt;/li&gt;
&lt;li&gt;Deploy &lt;strong&gt;dual-read mappers&lt;/strong&gt; in every client — the React SPA, the Flutter tablet and the mobile app, simultaneously — so both id dialects are understood during the phase.&lt;/li&gt;
&lt;li&gt;Flip the API surface to emit v2 ids as canonical.&lt;/li&gt;
&lt;li&gt;Burn the legacy ids out of requests, then out of responses.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Some families were mechanical. Some — like insurance branches — needed live data remapping mid-phase. Each phase was small enough to verify and, critically, small enough to roll back.&lt;/p&gt;
&lt;h2&gt;What made it survivable&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dual-read, not dual-write, in the clients.&lt;/strong&gt; Clients never chose which id to send; they only had to &lt;em&gt;understand&lt;/em&gt; both while a phase was in flight. Choice stayed on the server, where it could be changed without an app-store release.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auditability per phase.&lt;/strong&gt; Each vocabulary flip had its own before/after checks — the same discipline as the table-family migrations, applied to identifiers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Naming the enemy.&lt;/strong&gt; Once we called the problem &quot;identity spaces&quot;, conversations changed. &quot;Which space is this id from?&quot; became a standard review question — the cheapest bug-catching tool we added all year.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The takeaway&lt;/h2&gt;
&lt;p&gt;Every integration has identity spaces, and every long-lived migration will eventually put two of them in the same request. The failure mode isn&apos;t an exception — it&apos;s a &lt;strong&gt;successful call returning the wrong entity&lt;/strong&gt;, which is as bad as data corruption gets while looking exactly like health.&lt;/p&gt;
&lt;p&gt;So: never trust &quot;they&apos;re basically the same number&quot;. Sample it. Measure it. Ours died on 55 files — and measuring cost an afternoon, while believing would have cost us production data.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Questions about migrations or API versioning? &lt;a href=&quot;mailto:cetinelv@gmail.com&quot;&gt;Reach out&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>The Scenario Runner: an internal tool that became our migration&apos;s parity gate</title><link>https://kaancetinel.dev/blog/scenario-runner/</link><guid isPermaLink="true">https://kaancetinel.dev/blog/scenario-runner/</guid><description>Testing a damage-management platform means fabricating deep object graphs — and during a live migration, fabricating them identically in two systems. So we built a tool for it.</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While re-platforming ARES — the 50-module damage-management system I wrote about in &lt;a href=&quot;/blog/zero-downtime-migration&quot;&gt;the zero-downtime migration post&lt;/a&gt; — we kept hitting the same wall, over and over:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;you can&apos;t test a damage file you can&apos;t create.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;The problem: state is expensive&lt;/h2&gt;
&lt;p&gt;In a damage-management platform, nothing interesting happens on an empty database. A single meaningful test case is a &lt;em&gt;deep object graph&lt;/em&gt;: a claim file with a vehicle, a customer, a policy with entitlements, assigned tasks in the right states, mandatory documents, expertise lines for parts and labor, maybe a spare-parts order halfway through its supply process.&lt;/p&gt;
&lt;p&gt;Building that state by hand through the UI takes the better part of an hour — click by click, screen by screen. And whatever you build is subtly different from what your teammate built yesterday, so &quot;it works on my file&quot; became our version of &quot;it works on my machine&quot;.&lt;/p&gt;
&lt;p&gt;Unit tests don&apos;t fill this gap. We had hundreds of Pest test files for the pieces. But the bugs that hurt were &lt;em&gt;between&lt;/em&gt; the pieces — a task state that blocks a document rule, an entitlement calculation that only breaks after a rejection-revision cycle. Those live in fully-formed files.&lt;/p&gt;
&lt;p&gt;And we had a deadline-shaped reason to care: the migration. Every table family we flipped to the new schema needed the answer to one question — &lt;strong&gt;does a file behave the same in v2 as it did in v1?&lt;/strong&gt; Answering that requires the &lt;em&gt;same file&lt;/em&gt;, twice, in two systems.&lt;/p&gt;
&lt;h2&gt;What we built&lt;/h2&gt;
&lt;p&gt;The Scenario Runner is an internal admin tool that fabricates complete, end-to-end test files on demand:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Presets.&lt;/strong&gt; A scenario is a named recipe: &quot;insurance file with unsourced parts&quot;, &quot;mini-repair file awaiting approval&quot;, &quot;file ready for delivery&quot;. Pick one, click run, get a fully-formed file — with every entity in the graph created and wired.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A v1/v2 toggle.&lt;/strong&gt; The same scenario can be materialized against the legacy path or the new one. That single switch is what turned a convenience tool into migration infrastructure.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Legacy Radar.&lt;/strong&gt; Alongside the created file, the tool surfaces which legacy surfaces the scenario still touches — so &quot;is this flow really v2-native yet?&quot; stopped being a matter of opinion.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One design decision mattered more than the rest: scenarios are built &lt;strong&gt;through the same domain services production uses&lt;/strong&gt;, not through raw inserts. A file fabricated by bypassing the business logic tells you nothing — worse, it can be a state the real system could never produce. Building through the front door keeps every generated file honest, and as a side effect it exercises the write paths themselves.&lt;/p&gt;
&lt;h2&gt;How it became the parity gate&lt;/h2&gt;
&lt;p&gt;Once the v1/v2 toggle existed, a workflow crystallized around it. Before a table family flipped to v2-primary, the same scenarios ran on both sides and the results were compared — statuses, computed amounts, timeline events, generated documents. Differences were either bugs to fix or intentional changes to document. Only when the scenario suite agreed did the flip proceed.&lt;/p&gt;
&lt;p&gt;That&apos;s what I mean by &lt;em&gt;parity gate&lt;/em&gt;: not a dashboard, not a meeting — a tool-enforced checkpoint that made &quot;v2 behaves the same&quot; a testable claim instead of a hopeful one.&lt;/p&gt;
&lt;p&gt;It paid off beyond the migration, too. Bug reports now come with a scenario name instead of a fragile list of reproduction steps. New teammates explore the domain by generating files and poking at them. QA stopped hoarding hand-crafted test files like family heirlooms.&lt;/p&gt;
&lt;h2&gt;What I&apos;d tell you if you&apos;re building one&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Build through your domain services, not your tables.&lt;/strong&gt; Fabricated state must be &lt;em&gt;reachable&lt;/em&gt; state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make scenarios named and shareable.&lt;/strong&gt; The value isn&apos;t creating a file — it&apos;s two people talking about the &lt;em&gt;same&lt;/em&gt; file.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If you&apos;re migrating, make the target switchable.&lt;/strong&gt; The moment a scenario can run against both systems, you have a parity instrument for free.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Surface what the scenario touched.&lt;/strong&gt; Our Legacy Radar turned &quot;are we done with the old system here?&quot; into something you can see.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Internal tools rarely make it into portfolios or conference talks, but this one moved a 50-module migration more than most features did. If you&apos;re staring down a long strangler migration, build your scenario runner &lt;em&gt;first&lt;/em&gt; — it&apos;s the instrument panel for everything that comes after.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Questions about migrations or internal tooling? &lt;a href=&quot;mailto:cetinelv@gmail.com&quot;&gt;Reach out&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>94 seconds to 1.9: ditching information_schema for pg_catalog</title><link>https://kaancetinel.dev/blog/pg-catalog-94s-to-2s/</link><guid isPermaLink="true">https://kaancetinel.dev/blog/pg-catalog-94s-to-2s/</guid><description>Our schema-mapping tool took a minute and a half to load. The fix wasn&apos;t caching or indexes — it was asking PostgreSQL the same question through a different door.</description><pubDate>Thu, 10 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;During the &lt;a href=&quot;/blog/zero-downtime-migration&quot;&gt;ARES migration&lt;/a&gt; we built an internal tool called the &lt;strong&gt;Schema Dictionary&lt;/strong&gt;: an explorer that maps every legacy table and column to its v2 counterpart. With two schemas totalling thousands of tables in flight, &quot;where does this column live now?&quot; was a question someone asked every day, and the tool was the answer.&lt;/p&gt;
&lt;p&gt;There was just one problem: the page took &lt;strong&gt;94 seconds&lt;/strong&gt; to load.&lt;/p&gt;
&lt;h2&gt;The obvious suspects were innocent&lt;/h2&gt;
&lt;p&gt;The tool&apos;s data model was trivial — read the schema metadata, join it with our mapping annotations, render a tree. No business tables were touched at all. Yet it was the slowest page in the entire admin.&lt;/p&gt;
&lt;p&gt;The culprit was the metadata read itself. Like most people, we&apos;d written the introspection against &lt;code&gt;information_schema&lt;/code&gt; — the SQL-standard views (&lt;code&gt;information_schema.columns&lt;/code&gt;, &lt;code&gt;.tables&lt;/code&gt;, &lt;code&gt;.key_column_usage&lt;/code&gt;, and friends) that every tutorial reaches for. It&apos;s portable, it&apos;s readable, and on a schema this size it&apos;s &lt;strong&gt;brutally slow&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Why information_schema crawls&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;information_schema&lt;/code&gt; isn&apos;t a set of tables — it&apos;s a layer of views defined &lt;em&gt;on top of&lt;/em&gt; PostgreSQL&apos;s real system catalogs. And those views do a lot of well-meaning work you didn&apos;t ask for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Per-row privilege checks.&lt;/strong&gt; The views filter to objects the current role can see, which means functions like &lt;code&gt;pg_has_role()&lt;/code&gt; evaluated over and over — for every column of every table.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standards-compliance joins.&lt;/strong&gt; Mapping PostgreSQL&apos;s internal model onto the SQL standard&apos;s shape takes multi-way joins across &lt;code&gt;pg_class&lt;/code&gt;, &lt;code&gt;pg_attribute&lt;/code&gt;, &lt;code&gt;pg_type&lt;/code&gt;, &lt;code&gt;pg_namespace&lt;/code&gt; and more — repeated inside each view you touch.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimizer opacity.&lt;/strong&gt; Query several of these views together (columns + constraints + foreign keys, as any schema explorer does) and the planner struggles to push conditions down through the view stack. Row estimates go sideways; nested loops bloom.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of this matters when you look up one table. It matters enormously when a tool enumerates &lt;em&gt;everything&lt;/em&gt; — twice, in our case, because we introspected both the legacy schema and the v2 schema to draw the mapping.&lt;/p&gt;
&lt;h2&gt;The fix: ask the catalogs directly&lt;/h2&gt;
&lt;p&gt;PostgreSQL&apos;s real metadata lives in &lt;code&gt;pg_catalog&lt;/code&gt; — &lt;code&gt;pg_class&lt;/code&gt; (relations), &lt;code&gt;pg_attribute&lt;/code&gt; (columns), &lt;code&gt;pg_constraint&lt;/code&gt; (keys and FKs), &lt;code&gt;pg_namespace&lt;/code&gt; (schemas), &lt;code&gt;pg_index&lt;/code&gt;, &lt;code&gt;pg_type&lt;/code&gt;. Querying them directly skips the view layer entirely: no per-row privilege theater, no standards translation, and the planner sees plain tables with real statistics.&lt;/p&gt;
&lt;p&gt;The rewrite was a day of work. A handful of queries shaped roughly like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT c.relname   AS table_name,
       a.attname   AS column_name,
       t.typname   AS data_type,
       a.attnotnull AS not_null
FROM pg_catalog.pg_attribute a
JOIN pg_catalog.pg_class c      ON c.oid = a.attrelid
JOIN pg_catalog.pg_namespace n  ON n.oid = c.relnamespace
JOIN pg_catalog.pg_type t       ON t.oid = a.atttypid
WHERE n.nspname = &apos;public&apos;
  AND c.relkind = &apos;r&apos;
  AND a.attnum &amp;gt; 0
  AND NOT a.attisdropped;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;...plus the same treatment for constraints and foreign keys, and one pass to stitch results in application code instead of joining views against views in SQL.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Result: 94 seconds → 1.9 seconds.&lt;/strong&gt; Same data, same page, same database. Fifty times faster by walking through a different door.&lt;/p&gt;
&lt;h2&gt;The honest trade-offs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Portability is gone.&lt;/strong&gt; &lt;code&gt;information_schema&lt;/code&gt; works on any SQL database; &lt;code&gt;pg_catalog&lt;/code&gt; is PostgreSQL&apos;s own. For an internal tool pinned to PostgreSQL forever, that trade costs nothing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Catalogs are internal API.&lt;/strong&gt; They&apos;re remarkably stable (the queries above run unchanged from PG 11 to PG 18 — we know, because our legacy database was on 11), but they &lt;em&gt;can&lt;/em&gt; change between major versions. Read the release notes when you upgrade.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You re-own privilege filtering.&lt;/strong&gt; The views hid objects you couldn&apos;t access; raw catalogs show everything. For an admin-only tool that&apos;s fine — just don&apos;t forget it in user-facing contexts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The takeaway&lt;/h2&gt;
&lt;p&gt;If a page that only reads &lt;em&gt;metadata&lt;/em&gt; is slow, suspect the metadata layer itself. &lt;code&gt;information_schema&lt;/code&gt; is a convenience view with real costs, and at &quot;explore the whole schema&quot; scale those costs dominate everything else. The catalogs underneath answer the same questions — just without the ceremony.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Questions about PostgreSQL internals or migration tooling? &lt;a href=&quot;mailto:cetinelv@gmail.com&quot;&gt;Reach out&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Re-platforming a 50-module Laravel monolith with zero downtime</title><link>https://kaancetinel.dev/blog/zero-downtime-migration/</link><guid isPermaLink="true">https://kaancetinel.dev/blog/zero-downtime-migration/</guid><description>How we moved a decade-old automotive damage platform onto a new normalized PostgreSQL schema — mirrored the writes, flipped the tables, then reversed the mirror until the legacy system became the copy.</description><pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;For over a decade, ARES has been the system automotive damage operations run on. Workshops, experts, insurers and integrations all depend on it — every day, all day. Which is exactly what makes replacing it hard: &lt;strong&gt;there is no maintenance window&lt;/strong&gt;. You can&apos;t tell an entire industry to pause while you migrate.&lt;/p&gt;
&lt;p&gt;This is the story of how we re-platformed a 15,600-commit CodeIgniter-era monolith into a &lt;strong&gt;50-module Laravel modular monolith&lt;/strong&gt; on a completely new normalized PostgreSQL schema, without the legacy system ever going down.&lt;/p&gt;
&lt;h2&gt;The problem with the old schema&lt;/h2&gt;
&lt;p&gt;A schema that grows organically for ten years accumulates debt in ways code doesn&apos;t. Columns reused for different meanings. Duplicate sources of truth. Table families that made sense for the business of 2015 but fight the business of today. At some point, every new feature costs more because of the foundation it stands on.&lt;/p&gt;
&lt;p&gt;The obvious answer — &quot;rewrite it&quot; — is also the classic trap. Big-bang rewrites of live enterprise systems fail so often it&apos;s a cliché. So we didn&apos;t do a big bang. We did a &lt;strong&gt;strangler migration whose dual-write changed direction mid-flight&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Act one: observer-driven dual-write&lt;/h2&gt;
&lt;p&gt;The core idea: the legacy system keeps running as-is, and every write to a legacy table is mirrored — through model observers — into the new normalized schema. The new schema stays continuously up to date without the legacy code ever knowing it exists.&lt;/p&gt;
&lt;p&gt;This gave us three things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zero risk to production.&lt;/strong&gt; If anything went wrong on the v2 side, the legacy system didn&apos;t care.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Real data from day one.&lt;/strong&gt; The new schema was populated by real production traffic, not by a one-off backfill that goes stale.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A way to verify.&lt;/strong&gt; We could compare both sides continuously and catch mapping mistakes while they were still cheap.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Identity resolution: the assumption that collapsed&lt;/h2&gt;
&lt;p&gt;The hardest part of running two schemas at once isn&apos;t writing to both — it&apos;s &lt;strong&gt;agreeing on what things are&lt;/strong&gt;. Everyone assumed the new file id and the legacy reference number were &quot;basically the same number&quot;. Then we sampled 55 production files: &lt;strong&gt;54 of them differed&lt;/strong&gt;, and the two id bands overlapped — a value could be a valid id in both spaces, pointing at different records. Silent data corruption waiting to happen.&lt;/p&gt;
&lt;p&gt;The fix was an eight-phase &lt;strong&gt;wire contract&lt;/strong&gt; program: every external API id space — vehicle taxonomy, provinces, policy types, branches, document types, campaigns, invoices — moved from legacy references to v2 ids, with &lt;strong&gt;dual-read mappers rolled out simultaneously across three clients&lt;/strong&gt; (the React SPA, the Flutter tablet and the mobile app), so old and new payloads could coexist during each phase.&lt;/p&gt;
&lt;h2&gt;Strangling table families — a concrete one&lt;/h2&gt;
&lt;p&gt;With dual-write keeping the schemas in sync, we retired legacy table families one by one: rewrite every read path, rewrite every write path, keep the downstream feeds alive, then drop the tables.&lt;/p&gt;
&lt;p&gt;The labor tables were the hardest, and took five phases: first the mini-repair flow, then the damage flows, then severing the pricing dependency, then an &lt;strong&gt;atomic migration of the entire labor-id space&lt;/strong&gt;, and finally a reconcile command before the DROP. The post-DROP review still surfaced &lt;strong&gt;nine live error paths&lt;/strong&gt; that only production traffic could find — each one hunted down the same week. Retire &lt;em&gt;families&lt;/em&gt;, not tables; entities travel in groups.&lt;/p&gt;
&lt;h2&gt;Act two: reversing the mirror&lt;/h2&gt;
&lt;p&gt;This is the part most migration stories skip. Mirroring legacy writes into v2 keeps v2 fresh — but v2 is still the copy. The real milestone is the flip: read paths repointed to v2 in six waves, roughly &lt;strong&gt;40 endpoints rewritten v2-native out of ~70 audited&lt;/strong&gt;, and then the mirror itself &lt;strong&gt;reversed direction&lt;/strong&gt; — v2 became the authoritative system, syncing &lt;em&gt;back&lt;/em&gt; into the legacy tables for the consumers still reading them.&lt;/p&gt;
&lt;p&gt;In August 2026 we removed the last legacy→v2 mirror leg and deleted the feature flags. The decade-old system is still queryable — but today it&apos;s the replica, not the source.&lt;/p&gt;
&lt;h2&gt;The supporting cast&lt;/h2&gt;
&lt;p&gt;A migration like this leans on infrastructure choices that don&apos;t show up in demos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;transactional outbox&lt;/strong&gt; with idempotency, so events published to the parts-supply platform never drift from what was committed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HMAC-signed messaging&lt;/strong&gt; for service-to-service calls and third-party webhooks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configurable workflow and document rule engines&lt;/strong&gt;, so damage processes are data instead of hardcoded logic.&lt;/li&gt;
&lt;li&gt;Internal tooling: a &lt;strong&gt;Scenario Runner&lt;/strong&gt; that fabricates end-to-end test files on demand, and a &lt;strong&gt;Schema Dictionary&lt;/strong&gt; that maps v1↔v2 tables — whose introspection dropped &lt;strong&gt;from 94 seconds to 1.9&lt;/strong&gt; the day we swapped information_schema for pg_catalog.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Across the program: 347 migration files, 236 test files, 533 merged MRs, 18 production releases.&lt;/p&gt;
&lt;h2&gt;What I&apos;d tell you if you&apos;re facing the same thing&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Dual-write via observers is boring, unglamorous — and exactly why it works. The legacy system never has to know.&lt;/li&gt;
&lt;li&gt;Spend your design budget on &lt;strong&gt;identity resolution&lt;/strong&gt;, and &lt;em&gt;measure&lt;/em&gt; your assumptions. Ours died on a sample of 55 files.&lt;/li&gt;
&lt;li&gt;Strangle table &lt;em&gt;families&lt;/em&gt;, not tables — and budget for the post-DROP hunt; production always knows one more read path than you do.&lt;/li&gt;
&lt;li&gt;The migration isn&apos;t done when the new system is up to date. It&apos;s done when the old one becomes the copy — plan the mirror reversal from day one.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;This is a condensed version of the story — if you want to talk migrations, &lt;a href=&quot;mailto:cetinelv@gmail.com&quot;&gt;reach out&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Agentic MD – Markdown Reader for macOS</title><link>https://kaancetinel.dev/projects/agentic-md/</link><guid isPermaLink="true">https://kaancetinel.dev/projects/agentic-md/</guid><description>My own product: a supercharged Markdown reader and workspace for macOS with an MCP bridge, letting AI assistants like Claude read, edit and annotate your documents in real time.</description><pubDate>Mon, 31 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;display: flex; gap: 20px; align-items: center; flex-wrap: wrap; margin-bottom: 10px;&quot;&amp;gt;
&amp;lt;a href=&quot;https://apps.apple.com/tr/app/agentic-md-markdown-reader/id6803898058?mt=12&quot;&amp;gt;
&amp;lt;img src=&quot;/appstore.png&quot; alt=&quot;Mac App Store&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;
&amp;lt;a href=&quot;https://github.com/trailon/Agentic-MD&quot;&amp;gt;
&amp;lt;img src=&quot;/webpage.png&quot; alt=&quot;GitHub&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Agentic MD is a modern Markdown reader and workspace built specifically for macOS, power users and &lt;strong&gt;AI workflows&lt;/strong&gt; — my own product, designed, built and shipped end-to-end.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technologies: Electron, React, TypeScript, Model Context Protocol (MCP)&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Key features&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Native MCP bridge&lt;/strong&gt;: connects with Claude Desktop, Codex and Cursor via stdio, so your AI assistant can read documents, edit markdown content and resolve comments in real time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Element-level commenting&lt;/strong&gt;: hover over any table, code block, Mermaid diagram or KaTeX formula and attach rich annotations with tags like #Review, #Fix, #Todo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rich rendering&lt;/strong&gt;: full GitHub Flavored Markdown, Mermaid.js flowcharts and sequence diagrams, LaTeX / KaTeX math notation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instant path caching&lt;/strong&gt; with live file-system watchers that sync external edits automatically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fast search&lt;/strong&gt;: in-document highlighting and global search across all documents and headings.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Kaneo – GitLab-Driven Task Board</title><link>https://kaancetinel.dev/projects/kaneo/</link><guid isPermaLink="true">https://kaancetinel.dev/projects/kaneo/</guid><description>An open-source project board bent to the company&apos;s workflow: tasks advance automatically on branch/MR events, blockers release dependent work when merged — plus an F1-themed contribution leaderboard.</description><pubDate>Sat, 15 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Kaneo is the company fork of an open-source project management tool (Hono API, React/Vite, PostgreSQL, WebSocket, MCP), organizing work in a three-layer request-pool → product → repo board structure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technologies: TypeScript monorepo, Hono, React, Drizzle ORM, PostgreSQL, WebSocket&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In August 2026 I extended it to fit the team&apos;s actual workflow (23 commits, +26k lines).&lt;/p&gt;
&lt;h3&gt;Highlights&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitLab integration&lt;/strong&gt;: branch and MR events advance tasks through board columns automatically — the board reflects reality without anyone dragging cards.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Task relations&lt;/strong&gt;: when a blocker merges, dependent tasks are released automatically; umbrella tasks can spawn sub-tasks in other projects.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-project views&lt;/strong&gt;: an &quot;All Tasks&quot; screen, project grouping, &quot;unassigned&quot; and &quot;just me&quot; quick filters.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Personal GP&lt;/strong&gt;: an F1-themed contribution leaderboard with selectable cars — because internal tools deserve fun too.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Garantili Oto Servis – Parts Supply Platform</title><link>https://kaancetinel.dev/projects/gos/</link><guid isPermaLink="true">https://kaancetinel.dev/projects/gos/</guid><description>The spare-parts supply platform of the damage ecosystem. I built the ARES↔GOS integration line on both sides: transactional outbox, HMAC-signed webhooks, RabbitMQ queues and the Angular screens on top.</description><pubDate>Wed, 15 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;GOS (Garantili Oto Servis) is the spare-parts supply platform of the damage ecosystem: parts from insurance claim files flow in, a robot price-query runs, experts review, offers are made and orders are placed against the Delta ERP (MSSQL).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technologies: Laravel 12, PHP 8.4, PostgreSQL, RabbitMQ, Spatie Webhook Client, Angular, Apollo GraphQL&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In spring 2026 I built the &lt;strong&gt;ARES↔GOS integration line on both sides&lt;/strong&gt; — 119 commits on the API, 74 on the web panel.&lt;/p&gt;
&lt;h3&gt;Highlights&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reliable event delivery&lt;/strong&gt;: ARES-side transactional outbox with idempotency, GOS-side webhook processing over RabbitMQ with signature verification — no lost or duplicated events between two Laravel systems.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The &quot;unsourced part&quot; process&lt;/strong&gt;: a per-line state machine covering the expert-office queue, assembly-type webhooks, offer responses and per-part cancellations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pricing &amp;amp; orders&lt;/strong&gt;: DB-driven supplier discount tables with brand-based insurance discounts, order placement for ARES files, Delta ERP status polling behind a cache-layered pull proxy, HMAC unified service identity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Angular panel&lt;/strong&gt;: the expert-office queue screen (card per line with robot snapshots), order tracking, inline offer price editing and responsive table layouts.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Destech Hasar – Destech Drive</title><link>https://kaancetinel.dev/projects/destech-drive/</link><guid isPermaLink="true">https://kaancetinel.dev/projects/destech-drive/</guid><description>The dealer &amp; branch app of the ARES ecosystem: repair-file tracking, photo/document upload and internal messaging. I&apos;m the sole active developer — and I modernized it end-to-end in a 5-phase program.</description><pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;display: flex; gap: 20px; align-items: center; flex-wrap: wrap; margin-bottom: 10px;&quot;&amp;gt;
&amp;lt;a href=&quot;https://apps.apple.com/tr/app/destech-drive/id1489139055&quot;&amp;gt;
&amp;lt;img src=&quot;/appstore.png&quot; alt=&quot;AppStore&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;
&amp;lt;a href=&quot;https://play.google.com/store/apps/details?id=com.destech.destechdrive&quot;&amp;gt;
&amp;lt;img src=&quot;/playstore.png&quot; alt=&quot;PlayStore&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Destech Drive is where dealers and branches live inside the ARES ecosystem: they track their repair files, upload photos and documents, follow statuses and message the operation center. The app consumes both the legacy ARES API and ARES V2.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technologies: Flutter, Provider MVVM, GetIt/Injectable, shadcn_ui, Firebase Remote Config, Codemagic + GitLab CI&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I&apos;m the app&apos;s &lt;strong&gt;main developer&lt;/strong&gt; in 2026 (86 of 125 commits) and ran its modernization as a planned five-phase program — each phase with its own plan, research, implementation and verification documents.&lt;/p&gt;
&lt;h3&gt;Highlights&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Re-foundation&lt;/strong&gt;: shadcn_ui design system with a theme service, router-based navigation with a tab shell (drawer removed), dark theme by default.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Single login + Remote Config&lt;/strong&gt;: removed the dealer/branch selection step — v2-first auth with legacy fallback, refresh-token sessions, credentials in secure storage, and environment URLs served from Firebase Remote Config so they can change without a release.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Document upload/viewing redesign&lt;/strong&gt;, Android 13+ media permissions, migration to the Android Photo Picker.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI&lt;/strong&gt;: GitLab pipeline building release-candidate and production Android artifacts from tags, plus GitBook user guides (EN/TR).&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Destech Hasar – Ototek</title><link>https://kaancetinel.dev/projects/ototek/</link><guid isPermaLink="true">https://kaancetinel.dev/projects/ototek/</guid><description>A damage-management tablet suite where three business lines live in one Flutter codebase: insurance expertise (RSS), workshop management (RSA) and vehicle inspection (ROEA) — in five languages with RTL support.</description><pubDate>Sun, 01 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;display: flex; gap: 20px; align-items: center; flex-wrap: wrap; margin-bottom: 10px;&quot;&amp;gt;
&amp;lt;a href=&quot;https://apps.apple.com/tr/app/ototek/id1562986328&quot;&amp;gt;
&amp;lt;img src=&quot;/appstore.png&quot; alt=&quot;AppStore&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;
&amp;lt;a href=&quot;https://play.google.com/store/apps/details?id=com.destech.ototek&quot;&amp;gt;
&amp;lt;img src=&quot;/playstore.png&quot; alt=&quot;PlayStore&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Ototek is the tablet of the ARES damage-management ecosystem: every repair type gathered into a single Flutter application, with three mini-apps sharing one codebase — &lt;strong&gt;RSS&lt;/strong&gt; (insurance damage expertise), &lt;strong&gt;RSA&lt;/strong&gt; (workshop management for technicians and chiefs) and &lt;strong&gt;ROEA&lt;/strong&gt; (vehicle inspection). Five languages (TR/EN/DE/AR/CS) with full Arabic RTL support.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technologies: Flutter, Riverpod/Provider, auto_route, gRPC + REST, Sentry, Firebase Remote Config, OneSignal, Codemagic CI&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Since February 2026 I&apos;ve been the senior mobile developer — and because I also wrote the backend of many of these features in ARES V2, I own them &lt;strong&gt;end-to-end&lt;/strong&gt; from API design to the tablet screen. 291 commits, 60 test files.&lt;/p&gt;
&lt;h3&gt;Highlights&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Shared ChatSection&lt;/strong&gt;: collapsed two separate messaging screens (RSS file notes, RSA work-order chat) into one shared component behind a repository contract — @mentions, photo attachments and &lt;strong&gt;voice notes&lt;/strong&gt; with hold-to-record, slide-to-cancel, slide-up-to-lock UX.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mini-repair flow end-to-end&lt;/strong&gt; for the damage consultant role: work order form, expertise, approve/reject/revise cycle, entitlement indicator — with a performance pass (prefetch + parallel + dedup, single batch mutation).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spare-parts module (SYPS)&lt;/strong&gt;: approval/decision flows, multi-team role switcher, ERP status badges, timeline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workshop (RSA)&lt;/strong&gt;: Gantt-style job scheduling, mandatory before/after photos, chief photo gallery, work-order messaging.&lt;/li&gt;
&lt;li&gt;Mandatory-document checklists, an OOM crash fix in photo rendering, Arabic l10n + RTL, and Android/iOS build-chain maintenance.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>ARES V2 – Automotive Damage &amp; Repair Platform</title><link>https://kaancetinel.dev/projects/ares-v2/</link><guid isPermaLink="true">https://kaancetinel.dev/projects/ares-v2/</guid><description>A 50-module enterprise platform re-built on a new normalized PostgreSQL schema — while the decade-old legacy system stayed live with zero downtime. 1,286 commits, 533 MRs merged, 18 production releases.</description><pubDate>Sun, 01 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;ARES V2 is the re-platforming of the automotive damage management system Turkey&apos;s insurance repair operations run on: claim files, work orders, expertise (parts/labor/paint), document management, task chains, payments and dealer/expert/insurer administration. The legacy system — a decade-old CodeIgniter 3 monolith with 15,600 commits — stayed fully live during the entire migration, with &lt;strong&gt;zero downtime&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technologies: Laravel 12, PHP 8.2, PostgreSQL 18 (CloudNativePG), Octane/FrankenPHP, Livewire, React 19, RabbitMQ, Redis, Kubernetes/ArgoCD&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I joined as an API developer in February 2026 and became the &lt;strong&gt;technical owner of the legacy→v2 migration program&lt;/strong&gt; in June: architecture decisions, schema design, releases and deploys, production incident response, code review and documentation.&lt;/p&gt;
&lt;h3&gt;What I built&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The dual-write arc&lt;/strong&gt;: legacy writes were first mirrored into the v2 schema through model observers — then table families were flipped to v2-primary, the mirror direction was &lt;strong&gt;reversed&lt;/strong&gt; (v2 became authoritative, syncing back to v1), and in August 2026 the last v1→v2 mirror leg was removed. v2 is now the single system of record.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Identity resolution &amp;amp; the wire contract&lt;/strong&gt;: files.id and the legacy reference turned out to be entirely different id spaces (54 of 55 sampled files differed). The fix was an 8-phase wire-contract program moving every external API id space to v2 — with dual-read mappers rolled out simultaneously across three clients (React SPA, Flutter tablet, mobile).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Strangler-pattern table retirement&lt;/strong&gt;: the legacy labor table family was retired in 5 phases — including an atomic labor-id space migration and a post-DROP review that hunted down 9 live error surfaces.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrations&lt;/strong&gt;: transactional outbox with idempotency for the parts-supply platform (GOS), HMAC-signed service-to-service auth, Delta ERP status proxy, configurable workflow and document rule engines.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Internal tooling&lt;/strong&gt;: a Scenario Runner that generates end-to-end test files, and a Schema Dictionary (v1↔v2 mapping explorer) whose introspection I took &lt;strong&gt;from 94s to 1.9s&lt;/strong&gt; by moving from information_schema to pg_catalog.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Product-tour infrastructure&lt;/strong&gt;: screen/tour/step/chain model with anchor-based targeting, role &amp;amp; team audiences and media steps — powering a no-code tour builder for the product team.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Production operations&lt;/strong&gt;: blue/green prod cutover, same-day incident-to-release cycles, 18 production release tags.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Measured outcomes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;50-module modular monolith serving web admin, React SPA, tablet and third-party integrations&lt;/li&gt;
&lt;li&gt;1,286 commits · 533 MRs merged · 347 migration files · 236 test files (Pest 4) · 97 docs&lt;/li&gt;
&lt;li&gt;Schema introspection: 94s → 1.9s; legacy labor tables fully retired; last mirror leg removed&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Orbina AI – Customer &amp; Admin Dashboards</title><link>https://kaancetinel.dev/projects/orbina-dashboard/</link><guid isPermaLink="true">https://kaancetinel.dev/projects/orbina-dashboard/</guid><description>Two dashboards built end-to-end for an AI chatbot startup: a real-time customer analytics dashboard and the admin dashboard used to manage the platform.</description><pubDate>Tue, 01 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;display: flex; gap: 20px; align-items: center; flex-wrap: wrap; margin-bottom: 10px;&quot;&amp;gt;
&amp;lt;a href=&quot;https://www.orbina.ai/&quot;&amp;gt;
&amp;lt;img src=&quot;/webpage.png&quot; alt=&quot;Official Website&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href=&quot;https://www.orbina.ai/&quot;&gt;Orbina AI&lt;/a&gt; provides AI chatbot solutions for businesses — the very chat assistant on this site runs on it. As a freelance engineer working with a fully remote team, I built two of their products end-to-end.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technologies: React, Next.js, Socket.IO&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;What I built&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Customer Dashboard&lt;/strong&gt;: a responsive analytics dashboard for &lt;strong&gt;behavioral insights&lt;/strong&gt; and &lt;strong&gt;chatbot management&lt;/strong&gt;, with &lt;strong&gt;lead collection&lt;/strong&gt; flows wired to the chatbot funnel.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Admin Dashboard&lt;/strong&gt; used to manage the platform itself.&lt;/li&gt;
&lt;li&gt;Real-time updates with &lt;strong&gt;Socket.IO&lt;/strong&gt; and third-party API integrations.&lt;/li&gt;
&lt;li&gt;Modular React components with optimized rendering, delivered production-ready under tight deadlines.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Otokonfor – Otokonfor Workplace</title><link>https://kaancetinel.dev/projects/k1otokonfor/</link><guid isPermaLink="true">https://kaancetinel.dev/projects/k1otokonfor/</guid><description>Two mobile apps of various services for cars. One app is for customers and other one is for service provider.</description><pubDate>Mon, 01 Jan 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;display: flex; gap: 20px; align-items: center; flex-wrap: wrap; margin-bottom: 10px;&quot;&amp;gt;
&amp;lt;a href=&quot;https://apps.apple.com/tr/developer/otokonfor-oto-yonetim-ve-destek-hizmetleri-anonim-sirketi/id1551787205&quot;&amp;gt;
&amp;lt;img src=&quot;/appstore.png&quot; alt=&quot;AppStore&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;a href=&quot;https://play.google.com/store/apps/developer?id=OtoKonfor+Oto+Y%C3%B6netim+ve+Destek+Hizmetleri+A.%C5%9E.&quot;&amp;gt;
&amp;lt;img src=&quot;/playstore.png&quot; alt=&quot;PlayStore&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;a href=&quot;https://www.otokonfor.com/&quot;&amp;gt;
&amp;lt;img src=&quot;/webpage.png&quot; alt=&quot;Official Website&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Two mobile apps of various services for cars. One app is for customers and other one is for service provider.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technologies: Flutter, Laravel&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I was in the development of &lt;strong&gt;two applications&lt;/strong&gt; for customer
and service use.
Employed REST API for &lt;strong&gt;backend&lt;/strong&gt; infrastructure and seamless app
performance.&lt;/p&gt;
</content:encoded></item><item><title>Tur Assist – TurApp</title><link>https://kaancetinel.dev/projects/k2turapp/</link><guid isPermaLink="true">https://kaancetinel.dev/projects/k2turapp/</guid><description>The field app of Turkey&apos;s largest roadside-assistance network — live job pools over GraphQL subscriptions, background geolocation and video calls. I was the main developer: 1,682 of 3,400 commits.</description><pubDate>Thu, 01 Jun 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;display: flex; gap: 20px; align-items: center; flex-wrap: wrap; margin-bottom: 10px;&quot;&amp;gt;
&amp;lt;a href=&quot;https://apps.apple.com/tr/app/turapp/id1637265119&quot;&amp;gt;
&amp;lt;img src=&quot;/appstore.png&quot; alt=&quot;AppStore&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;
&amp;lt;a href=&quot;https://play.google.com/store/apps/details?id=com.destechhasar.tur_sys_app&quot;&amp;gt;
&amp;lt;img src=&quot;/playstore.png&quot; alt=&quot;PlayStore&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;TurApp is the Flutter app that Tur Assist — Turkey&apos;s largest roadside assistance and towing network — puts in the hands of its field personnel and partner companies: pending assignments, active services, arrival tracking, replacement vehicles, residential services, vehicle load inspection, shift management and video calls.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technologies: Flutter, GraphQL (queries/mutations/subscriptions with generated schema), Provider + get_it/injectable, OneSignal, Firebase, flutter_background_geolocation, Yandex MapKit, biometric auth&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I was the &lt;strong&gt;main developer&lt;/strong&gt; from January 2023 to December 2024 — 1,682 of the repo&apos;s 3,400 commits (49%) are mine, taking the product from a near-empty codebase to the App Store and Google Play.&lt;/p&gt;
&lt;h3&gt;Highlights&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Live job pool over GraphQL subscriptions&lt;/strong&gt; — including the assignment logic: when a company rejects a job, the pool clears and the next company is assigned.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Background location worker&lt;/strong&gt;: battery-aware tracking with isolate workers, feeding real-time arrival estimates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Push migration in production&lt;/strong&gt;: moved the notification stack from Firebase Messaging to OneSignal without disrupting live operations.&lt;/li&gt;
&lt;li&gt;Biometric login, in-app product tours, admin panel views, video calling, a lite mode for low-end devices.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Mevbank - Lebib Yalkın</title><link>https://kaancetinel.dev/projects/k4mevbank/</link><guid isPermaLink="true">https://kaancetinel.dev/projects/k4mevbank/</guid><description>MevBank is a comprehensive legal database developed by Lebib Yalkın with over 70 years of expertise. It allows users to instantly access up-to-date laws, decrees, regulations, and amendments in Turkish legislation. Designed for legal professionals, it ensures fast, reliable, and accurate access to current legal content.</description><pubDate>Sat, 21 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;display: flex; gap: 20px; align-items: center; flex-wrap: wrap; margin-bottom: 10px;&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;a href=&quot;https://apps.apple.com/ee/app/mevbank/id1599243353&quot;&amp;gt;
&amp;lt;img src=&quot;/appstore.png&quot; alt=&quot;AppStore&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;a href=&quot;https://play.google.com/store/apps/details?id=com.mevbank&quot;&amp;gt;
&amp;lt;img src=&quot;/playstore.png&quot; alt=&quot;PlayStore&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;a href=&quot;https://lebibyalkin.com.tr/&quot;&amp;gt;
&amp;lt;img src=&quot;/webpage.png&quot; alt=&quot;Official Website&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;MevBank&lt;/strong&gt; is a comprehensive legal database developed by Lebib Yalkın with over 70 years of expertise.
It allows users to instantly access up-to-date laws, decrees, regulations, and amendments in Turkish legislation.
Designed for legal professionals, it ensures fast, reliable, and accurate access to current legal content.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technologies: Flutter, Laravel, GraphQL&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Independently developed the mobile application, designing the
foundational template.
Gained insights into the legal industry and subscription-based payment
systems.&lt;/p&gt;
</content:encoded></item><item><title>Catchpad - Catchpad</title><link>https://kaancetinel.dev/projects/k3catchpad/</link><guid isPermaLink="true">https://kaancetinel.dev/projects/k3catchpad/</guid><description>This app allows its users to experience sport/education/health activities integrated with a mechanical disk pad. Pad has various sensors that calculates distance/acceleration/balance and these are used for related activities in the app. App controls pad via BLE communication.</description><pubDate>Sat, 21 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;display: flex; gap: 20px; align-items: center; flex-wrap: wrap; margin-bottom: 10px;&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;a href=&quot;https://apps.apple.com/tr/app/catchpad-i-nteraktif-egzersiz/id1625783946?l=tr&quot;&amp;gt;
&amp;lt;img src=&quot;/appstore.png&quot; alt=&quot;AppStore&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;a href=&quot;https://play.google.com/store/apps/details?id=com.catchpad&quot;&amp;gt;
&amp;lt;img src=&quot;/playstore.png&quot; alt=&quot;PlayStore&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;a href=&quot;https://catchpad.com/&quot;&amp;gt;
&amp;lt;img src=&quot;/webpage.png&quot; alt=&quot;Official Website&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;This app allows its users to experience sport/education/health activities integrated with a mechanical disk pad. Pad has various sensors that calculates distance/acceleration/balance and these are used for related activities in the app. App controls pad via BLE communication.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technologies: Flutter, Firebase, JavaScript&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Solely developed the mobile app, managed the website, and initiated
backend development.
Transitioned to Firebase Firestore for resource-efficient development
in a startup environment.&lt;/p&gt;
</content:encoded></item><item><title>TaksiMaxi - TaksidePos</title><link>https://kaancetinel.dev/projects/k5taksimaxi/</link><guid isPermaLink="true">https://kaancetinel.dev/projects/k5taksimaxi/</guid><description>TaksiMaxi is a dual mobile application platform developed for TaksidePos, offering seamless taxi booking for passengers and real-time ride management for drivers. Inspired by BiTaksi, it was designed to connect riders with nearby taxi drivers quickly and securely. I was solely responsible for the app’s design and development; although fully functional, the project was shelved due to internal strategic changes.</description><pubDate>Sat, 21 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;display: flex; gap: 20px; align-items: center; flex-wrap: wrap; margin-bottom: 10px;&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;a href=&quot;https://apps.apple.com/ee/app/mevbank/id1599243353&quot;&amp;gt;
&amp;lt;img src=&quot;/appstore.png&quot; alt=&quot;AppStore&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;a href=&quot;https://play.google.com/store/apps/details?id=com.mevbank&quot;&amp;gt;
&amp;lt;img src=&quot;/playstore.png&quot; alt=&quot;PlayStore&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;a href=&quot;https://lebibyalkin.com.tr/&quot;&amp;gt;
&amp;lt;img src=&quot;/webpage.png&quot; alt=&quot;Official Website&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;TaksiMaxi&lt;/strong&gt; is a dual mobile application platform developed for TaksidePos, offering seamless taxi booking for passengers and real-time ride management for drivers.
Inspired by BiTaksi, it was designed to connect riders with nearby taxi drivers quickly and securely.
I was solely responsible for the app’s design and development; although fully functional, the project was shelved due to internal strategic changes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technologies: Flutter, Laravel, REST API, Socket.IO&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Independently developed and designed the core structure of the mobile
app.
Contributed to the service industry with innovative solutions.&lt;/p&gt;
</content:encoded></item><item><title>Sembol Nakliyat - Sembol</title><link>https://kaancetinel.dev/projects/k6sembolnakliyat/</link><guid isPermaLink="true">https://kaancetinel.dev/projects/k6sembolnakliyat/</guid><description>Sembol Nakliyat’s warehouse occupancy management app provides real-time tracking of storage usage across multiple locations. It helps optimize space utilization, monitor capacity status, and streamline daily logistics operations. I developed the app to improve operational efficiency and provide better control over warehouse activities.</description><pubDate>Sat, 21 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;div style=&quot;display: flex; gap: 20px; align-items: center; flex-wrap: wrap; margin-bottom: 10px;&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;a href=&quot;https://www.sembolevdeneve.com/&quot;&amp;gt;
&amp;lt;img src=&quot;/webpage.png&quot; alt=&quot;Official Website&quot; style=&quot;height: 50px;&quot; /&amp;gt;
&amp;lt;/a&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Sembol Nakliyat’s&lt;/strong&gt; warehouse occupancy management app provides real-time tracking of storage usage across multiple locations.
It helps optimize space utilization, monitor capacity status, and streamline daily logistics operations.
I developed the app to improve operational efficiency and provide better control over warehouse activities.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Technologies: Flutter, Laravel, REST API&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Revitalized the mobile app by collaborating with a team of three
developers.
Acquired operational knowledge in the transportation industry.&lt;/p&gt;
</content:encoded></item></channel></rss>