{
  "schemaVersion": "1.0",
  "defaultLocale": "tr",
  "locales": {
    "tr": {
      "canonicalUrl": "https://www.pitonmert.com/",
      "about": {
        "id": "about:mert-atci",
        "locale": "tr",
        "title": "Mert Atcı",
        "canonicalUrl": "https://www.pitonmert.com/",
        "content": "# Mert Atcı\n\nFull Stack .NET Developer\n\nASP.NET Core ve React ile web uygulamaları geliştiriyorum. Kelime öğrenme, portföy takibi ve üretim hattı izleme gibi farklı ihtiyaçlara yönelik projelerimde veri modelinden kullanıcı arayüzüne, testlerden dağıtıma kadar tüm süreci yönetiyorum. Ağırlıklı olarak backend geliştirmeye odaklanıyor; kurduğum sistemlerin nasıl çalıştığını ve teknik kararlarımı burada paylaşıyorum.\n\n[E-posta](mailto:mertatci@pitonmert.com) · [CV İndir](https://www.pitonmert.com/cv/mert-atci-cv.pdf)\n\n[GitHub](https://github.com/pitonmert) · [LinkedIn](https://linkedin.com/in/pitonmert)\n\n## Teknik Yetkinlikler\n\n### Backend & Veri\n\nC#, ASP.NET Core, EF Core, PostgreSQL, ASP.NET Core Identity\n\n### Frontend\n\nReact, TypeScript, Tailwind CSS\n\n### Test & Kalite\n\nxUnit, Testcontainers, Vitest\n\n### Dağıtım & Entegrasyon\n\nDocker, nginx, GitHub Actions, Cloudflare Tunnel, Cloudflare Access, SignalR, MQTT\n\n## Eğitim\n\n### KTO Karatay Üniversitesi\n\nBilgisayar Programcılığı · Ön Lisans\n\nKonya · Eylül 2024 – Haziran 2026\n\nNot ortalaması: 3.01 / 4.00",
        "contentFormat": "markdown",
        "kind": "about",
        "person": {
          "name": "Mert Atcı",
          "role": "Full Stack .NET Developer",
          "summary": "ASP.NET Core ve React ile web uygulamaları geliştiriyorum. Kelime öğrenme, portföy takibi ve üretim hattı izleme gibi farklı ihtiyaçlara yönelik projelerimde veri modelinden kullanıcı arayüzüne, testlerden dağıtıma kadar tüm süreci yönetiyorum. Ağırlıklı olarak backend geliştirmeye odaklanıyor; kurduğum sistemlerin nasıl çalıştığını ve teknik kararlarımı burada paylaşıyorum.",
          "email": "mertatci@pitonmert.com",
          "cvUrl": "https://www.pitonmert.com/cv/mert-atci-cv.pdf"
        },
        "socialLinks": [
          {
            "label": "GitHub",
            "url": "https://github.com/pitonmert"
          },
          {
            "label": "LinkedIn",
            "url": "https://linkedin.com/in/pitonmert"
          }
        ],
        "skills": [
          {
            "id": "backend",
            "title": "Backend & Veri",
            "technologies": [
              "C#",
              "ASP.NET Core",
              "EF Core",
              "PostgreSQL",
              "ASP.NET Core Identity"
            ]
          },
          {
            "id": "frontend",
            "title": "Frontend",
            "technologies": [
              "React",
              "TypeScript",
              "Tailwind CSS"
            ]
          },
          {
            "id": "testing",
            "title": "Test & Kalite",
            "technologies": [
              "xUnit",
              "Testcontainers",
              "Vitest"
            ]
          },
          {
            "id": "deployment",
            "title": "Dağıtım & Entegrasyon",
            "technologies": [
              "Docker",
              "nginx",
              "GitHub Actions",
              "Cloudflare Tunnel",
              "Cloudflare Access",
              "SignalR",
              "MQTT"
            ]
          }
        ],
        "education": [
          {
            "id": "education:kto-karatay",
            "school": "KTO Karatay Üniversitesi",
            "degree": "Bilgisayar Programcılığı · Ön Lisans",
            "location": "Konya",
            "period": "Eylül 2024 – Haziran 2026",
            "grade": "Not ortalaması: 3.01 / 4.00"
          }
        ]
      },
      "experience": [
        {
          "id": "experience:ae-yazilim-internship",
          "locale": "tr",
          "title": "Stajyer · AE Yazılım",
          "canonicalUrl": "https://www.pitonmert.com/#experience",
          "content": "# Stajyer · AE Yazılım\n\nKonya · Haziran – Ağustos 2025\n\nUnity ve C# ile çocuklara yönelik LetterHop kelime oyununu geliştirdim. REST API’den dinamik içerik, sesli okuma, kalıcı ilerleme takibi ve AdMob entegrasyonu üzerinde çalıştım.",
          "contentFormat": "markdown",
          "kind": "experience",
          "role": "Stajyer",
          "company": "AE Yazılım",
          "location": "Konya",
          "period": "Haziran – Ağustos 2025",
          "description": "Unity ve C# ile çocuklara yönelik LetterHop kelime oyununu geliştirdim. REST API’den dinamik içerik, sesli okuma, kalıcı ilerleme takibi ve AdMob entegrasyonu üzerinde çalıştım."
        }
      ],
      "projects": [
        {
          "id": "project:release-radar",
          "locale": "tr",
          "title": "release-radar",
          "canonicalUrl": "https://www.pitonmert.com/projects/release-radar",
          "content": "## Bağlam ve amaç\n\nrelease-radar, takip edilen araçların sürüm notlarını farklı sitelerde aramak yerine tek bir bildirim akışında toplar. Sistem, yeni sürümü bulmakla yetinmez; uzun veya dağınık notları Türkçe özetler ve kaynağa geri dönülebilen bir Telegram bildirimi üretir.\n\nBu proje, zamanlanmış tek seferlik bir iş yerine kendi döngüsünü yöneten uzun ömürlü bir servis olarak tasarlandı. Böylece tarama sıklığı, bildirim geçmişi ve hata sonrası yeniden deneme davranışı uygulamanın kendisinde kalır.\n\n## Nasıl çalışır\n\nServis açıldığında kaynak yapılandırmasını okur ve ilk taramayı yapar. Bir kaynak ilk kez görülüyorsa mevcut girişler bildirim göndermeden başlangıç noktası olarak kaydedilir. Sonraki taramalarda yalnız daha önce görülmemiş girişler işlenir.\n\nYeni bir sürüm bulunduğunda kaynak türüne göre RSS veya Atom girdisi alınır; VS Code için ayrıntılı Markdown notlarını getiren özel yol da bulunur. Metin Gemini API ile Türkçe özetlenir, Telegram sınırını aşarsa parçalara bölünür ve gönderilir. Başarılı gönderimden sonra giriş kalıcı duruma yazılır.\n\n## Sistem tasarımı\n\nPython uygulamasındaki ana tarama döngüsü; kaynak okuma, ağ isteği, özetleme ve bildirim işlerini bir araya getirir. SQLite veritabanı, kaynak adı ve giriş kimliğinden oluşan benzersiz anahtarla daha önce işlenen sürümleri saklar.\n\nYapılandırma dosyası izlenecek kaynakları ve kaynak türlerini tanımlar. Docker Compose, yapılandırmayı salt okunur bağlar; veritabanı ise container yeniden kurulsa da korunacak kalıcı bir dizinde tutulur. Böylece kaynak listesi değiştirilebilirken bildirim geçmişi çalışma zamanına ait kalır.\n\n## Öne çıkan teknik kararlar\n\nİlk taramada sessiz baseline oluşturmak, eski sürümlerin bir anda bildirim olarak yağmasını önler. SQLite içindeki source ve guid birleşik anahtarı, container yeniden başlasa bile aynı kaydın tekrar gönderilmesini engeller.\n\nHarici ağ, Gemini ve Telegram çağrıları geçici olarak başarısız olabilir. Bu nedenle istekler geri çekilmeli denemelerle yapılır; Telegram gönderimi başarılı olmadan bir giriş görüldü olarak işaretlenmez. Uzun mesajların parçalanması, bir sürüm notunun platform sınırları yüzünden tamamen kaybolmasını önler.\n\nServis kapanış sinyallerini ele alır ve bekleme döngüsünü kesintili uyku ile yürütür. Bu, Compose yeniden başlatmalarında bir sonraki döngünün daha öngörülebilir başlamasını sağlar.\n\n## Doğrulama ve sınırlar\n\nPytest testleri; SQLite şemasını, başlangıç baseline davranışını, tekrar bildirimi önlemeyi ve başarısız Telegram gönderiminde kaydın işaretlenmemesini kapsar. Ruff ve Docker build kontrolleri de sürekli entegrasyon akışında yer alır.\n\nÖzetin doğruluğu kaynak metne ve dil modelinin çıktısına bağlıdır. Telegram gönderimi ile SQLite güncellemesi tek atomik işlem değildir; iki adım arasında kesinti olursa sonraki taramada yinelenen bildirim oluşabilir. Kalıcı veri dizininin silinmesi de her kaynak için başlangıç noktasını yeniden kurar.",
          "contentFormat": "markdown",
          "publishedAt": "2026-09-12T00:00:00.000Z",
          "updatedAt": "2026-09-12T00:00:00.000Z",
          "kind": "project",
          "description": "Yazılım sürüm notlarını düzenli tarayan, Türkçe özetleyen ve Telegram üzerinden ileten kalıcı otomasyon servisi.",
          "repositoryUrl": "https://github.com/pitonmert/release-radar",
          "technologies": [
            "Python",
            "SQLite",
            "Gemini API",
            "Telegram Bot API",
            "Docker"
          ],
          "categories": [
            "Otomasyon",
            "Servis"
          ],
          "createdAt": "2026-05-17T13:03:04.000Z",
          "featured": false
        },
        {
          "id": "project:portfolio-tracker",
          "locale": "tr",
          "title": "portfolio-tracker",
          "canonicalUrl": "https://www.pitonmert.com/projects/portfolio-tracker",
          "content": "## Bağlam ve amaç\n\nportfolio-tracker, farklı kaynaklardaki yatırım varlıklarını ve nakit bakiyesini tek bir kişisel çalışma alanında takip etmek için geliştirildi. Uygulama, toplam değeri yalnızca fiyatlardan üretmez; alış ve satış hareketlerini korur, açık ve kapanmış pozisyonları bu hareketlerden hesaplar.\n\nOdak noktası finansal tavsiye vermek değil, portföy verisinin nasıl modellendiğini görünür ve denetlenebilir kılmaktır. Bu nedenle işlem geçmişi, fiyat kaynağı, hesaplama ve arayüz birbirinden ayrılmıştır.\n\n## Nasıl çalışır\n\nKullanıcı bir varlık için alış veya satış işlemi eklediğinde kayıt önce işlem defterine yazılır. API, varlığın işlem geçmişi ile son fiyatını birleştirerek miktar, maliyet, gerçekleşmiş kâr/zarar ve gerçekleşmemiş kâr/zarar değerlerini hesaplar.\n\nDashboard; açık pozisyonları, kapanmış pozisyonları, nakit bakiyesini ve portföy özetini bu sonuçlardan okur. Piyasa verisi uygun olduğunda ayrı servis üzerinden alınır; uygun olmayan veya eksik sağlayıcı verisi için elle fiyat girilebilir. Fiyat yenileme hem periyodik arka plan işi hem de istek tabanlı kuyruk akışıyla desteklenir.\n\n## Sistem tasarımı\n\nReact ve TypeScript istemcisi, ASP.NET Core API ile aynı origin altında konuşur. API; EF Core aracılığıyla PostgreSQL'e erişir ve piyasa verisi gerektiğinde FastAPI tabanlı market-data-service'e HTTP isteği yapar. Python servisinin sağlayıcı seçimi ile finansal hesapların sahibi olan .NET API'nin sorumlulukları bu sayede ayrılır.\n\nTemel veri modeli dört parçadan oluşur:\n\n- **Transactions**, portföy hareketlerinin değiştirilemeyen kaynak kaydıdır.\n- **Assets**, katalogdan gelen veya kullanıcı tarafından oluşturulan varlık tanımını taşır.\n- **MarketPrices**, sağlayıcıdan ya da kullanıcıdan gelen en güncel fiyatı saklar.\n- **PortfolioPositions**, arayüzün hızlı okuması için üretilen, yeniden kurulabilir pozisyon modelidir.\n\n## Öne çıkan teknik kararlar\n\nİşlem defterini kaynak gerçek, pozisyon tablosunu ise okuma modeli olarak tutmak kritik bir seçimdir. Bir hesaplama kuralı veya fiyat verisi değiştiğinde pozisyonlar işlemlerden yeniden üretilebilir; geçmiş hareketlerin anlamı arayüz toplamlarına bağımlı kalmaz.\n\nMaliyet hesabı ağırlıklı ortalama maliyet yöntemiyle yürütülür. Satışlar kalan maliyet tabanını günceller, gerçekleşmiş sonuçlar ayrı izlenir ve küçük miktar kalıntıları kapanış toleransıyla ele alınır. Hesaplama kuralları UI içinde tekrar edilmez; API tek hesaplama sahibi olarak kalır.\n\nPiyasa verisini ayrı FastAPI servisinde tutmak, borsapy gibi sağlayıcı bağımlılıklarını uygulamanın finansal çekirdeğinden ayırır. Sağlayıcı sembolleri normalize edilir ve fiyatın kullanılabilir olup olmadığı sözleşme üzerinden API'ye aktarılır.\n\n## Doğrulama ve sınırlar\n\nAPI katmanında Testcontainers ile gerçek PostgreSQL kullanan entegrasyon testleri, React tarafında Vitest ve Testing Library, Python servisinde pytest kontrolleri bulunur. Bu yaklaşım; hesaplama, HTTP sözleşmesi ve piyasa verisi köprüsünü ayrı ayrı doğrulamayı sağlar.\n\nUygulama kişisel ve eğitim amaçlıdır; çok kullanıcılı bir yatırım platformu veya finansal danışmanlık sistemi değildir. Piyasa verisinin doğruluğu ve sürekliliği dış sağlayıcılara bağlıdır. Eksik fiyat, bir yatırım kararını uygulamanın kendisinin verebileceği anlamına gelmez.",
          "contentFormat": "markdown",
          "publishedAt": "2026-09-12T00:00:00.000Z",
          "updatedAt": "2026-09-12T00:00:00.000Z",
          "kind": "project",
          "description": "İşlem kayıtlarını kaynak gerçek olarak koruyan, fiyat verisi ve portföy hesaplarını ayrı sorumluluklarda yöneten kişisel portföy uygulaması.",
          "repositoryUrl": "https://github.com/pitonmert/portfolio-tracker",
          "technologies": [
            "ASP.NET Core",
            "React",
            "TypeScript",
            "EF Core",
            "PostgreSQL",
            "FastAPI",
            "Testcontainers"
          ],
          "categories": [
            "Web Uygulaması",
            "Veri Modelleme"
          ],
          "createdAt": "2026-04-28T09:01:29.000Z",
          "featured": true,
          "featuredOrder": 0
        },
        {
          "id": "project:word-match",
          "locale": "tr",
          "title": "word-match",
          "canonicalUrl": "https://www.pitonmert.com/projects/word-match",
          "content": "## Bağlam ve amaç\n\nword-match, Türkçe konuşan kullanıcıların İngilizce kelime çalışmasını rastgele soru çözme deneyiminden çıkarıp düzenli bir öğrenme akışına dönüştürür. Uygulama, kullanıcının seçmesi gereken çok sayıda mod yerine tek bir Study yüzeyi sunar; sistem sıradaki uygun konuyu belirler, kullanıcı isterse konu seçimini değiştirebilir.\n\nAmaç yalnızca doğru cevap saymak değildir. Her kelimenin hangi beceride ne zaman tekrar edilmesi gerektiğini takip eden kalıcı bir ilerleme modeli kurulur. Oturumlar, cevaplar ve mastery verisi kullanıcı hesabıyla birlikte saklanır.\n\n## Nasıl çalışır\n\nCurriculum; seviye, sıralı konu, açık öğrenme grubu ve sıralı kelime katmanlarından oluşur. Study ana sayfası kullanıcının devam eden oturumunu ya da sıradaki uygun konuyu gösterir. Bir konu oturumu, gruptaki kelimelerin önce anlamını tanıma ardından yazılı hatırlama tarafını dengeli biçimde çalıştırır.\n\nCevap sonrasında API, soru sonucunu kaydeder ve ilgili kelimenin mastery durumunu günceller. Kullanıcı cevaplanması uygun olmayan yazma sorularını kısa süreli erteleyebilir; bu seçim cevap olarak sayılmaz. Tamamlanan veya yarım kalan oturumlar daha sonra ayrıntılı sonuçlarıyla görüntülenebilir.\n\n## Sistem tasarımı\n\nASP.NET Core API; kimlik doğrulama, Study planlama, soru üretimi, kelime kataloğu ve veri erişimini feature odaklı biçimde taşır. React istemci, oturum durumunu ve API isteklerini ayrı feature'larda yönetir. PostgreSQL; kullanıcı, curriculum, oturum, soru snapshot'ı ve mastery kayıtlarını kalıcı tutar.\n\nİçerik verisi CSV tabanlıdır. Kaynaktaki sabit ImportKey değeri, veritabanının ürettiği kimlikten ayrıdır; idempotent bootstrap komutu bu anahtarla kelimeleri ve curriculum yerleşimini günceller. Böylece içerik düzenlemesi kullanıcı ilerlemesini gereksiz yere sıfırlamaz.\n\n## Öne çıkan teknik kararlar\n\nSoru üretimi, Study oturumu oluşturulduğu anda planlanır ve soru/cevap snapshot'ları saklanır. Bu sayede sonraki katalog değişiklikleri devam eden bir oturumun geçmişini değiştirmez. Çoktan seçmeli sorularda yanlış seçenekler önce oturum bağlamından, sonra aynı seviyeden ve gerekirse geniş katalogdan seçilir.\n\nMastery, tek bir genel puan yerine beceri boyutları üzerinden ilerler. Doğru sonuçlar tekrar zamanını ileri taşırken yanlış veya bilmiyorum sonuçları uygun beceriyi kısa geri dönüşe çeker. Planlayıcı, yalnızca hazır olan kelimeleri seçer ve pekiştirme oturumlarını sınırlı sayıda soruyla tutar.\n\nOturum yazımı, cevap kaydı ve ilerleme güncellemesi aynı veri bütünlüğü sınırında ele alınır. Kimlik doğrulama tarafında Identity cookie'leri ve durum değiştiren isteklerde XSRF koruması kullanılır.\n\n## Doğrulama ve sınırlar\n\nAPI tarafında Study planlama, soru üretimi, bootstrap ve endpoint davranışını kapsayan testler; istemci tarafında oturum, kimlik doğrulama ve kelime arayüzü testleri bulunur. Docker ve nginx yapılandırması, uygulamanın üretim benzeri bir ağ sınırında çalışmasını destekler.\n\nŞu anki ürün odağı curriculum tabanlı Study akışıdır. Daha geniş içerik türleri, gelişmiş telaffuz değerlendirmesi ve sosyal/puan sistemleri bu çekirdeğin parçası değildir. Kelime içeriğinin niteliği kaynak CSV'nin doğruluğuna bağlıdır; uygulama bir dil öğretmeni yerine düzenli çalışma altyapısı sağlar.",
          "contentFormat": "markdown",
          "publishedAt": "2026-09-12T00:00:00.000Z",
          "updatedAt": "2026-09-12T00:00:00.000Z",
          "kind": "project",
          "description": "Müfredat sırası, kelime bazlı mastery ve kalıcı Study oturumlarıyla Türkçe konuşan kullanıcılar için İngilizce kelime öğrenme uygulaması.",
          "repositoryUrl": "https://github.com/pitonmert/word-match",
          "technologies": [
            "ASP.NET Core",
            "React",
            "TypeScript",
            "EF Core",
            "PostgreSQL",
            "ASP.NET Core Identity",
            "Docker"
          ],
          "categories": [
            "Web Uygulaması",
            "Eğitim Teknolojisi"
          ],
          "createdAt": "2026-03-17T10:55:39.000Z",
          "featured": true,
          "featuredOrder": 2
        },
        {
          "id": "project:smart-line",
          "locale": "tr",
          "title": "smart-line",
          "canonicalUrl": "https://www.pitonmert.com/projects/smart-line",
          "content": "## Bağlam ve amaç\n\nsmart-line, üretim hattındaki manuel ayırma ve izleme işini tek bir operatör yüzeyinde toplamak için tasarlanmış bir IIoT prototipidir. Amaç yalnızca sensör değerlerini göstermek değildir: ürün akışını, çevresel koşulları, yangın durumunu ve konveyör komutlarını aynı sistemin açık sorumlulukları haline getirmektir.\n\nProje; cihaz yazılımı, görüntü tabanlı veri toplama, mesajlaşma altyapısı, uygulama API'si ve dashboard arasında sınırları belirgin bir sistem kurar. Bu nedenle web arayüzü, fiziksel cihazlar ve güvenlik davranışı birbirinden bağımsız geliştirilebilir ve test edilebilir.\n\n## Nasıl çalışır\n\nESP32 firmware; ortam ve yangın verisini üretir. Ayrı bir Python vision servisi barkod ve QR sonuçlarını hazırlar; ESP32-CAM ise görüntü akışını sağlar. Cihaz ve vision mesajları, TLS ile korunan MQTT broker üzerinden API'ye ulaşır.\n\nAPI arka plan servisleri bu mesajları işler, PostgreSQL'e kalıcı kayıtlar yazar ve SignalR ile dashboard'a canlı güncellemeler gönderir. Operatör; güncel ölçümleri, uyarıları, üretim hareketlerini ve konveyör durumunu aynı arayüzden izler. Komutlar doğrudan cihaza gitmez; API içindeki yetkilendirme ve güvenlik akışından geçer.\n\n## Sistem tasarımı\n\nSistem, her biri net bir sorumluluk taşıyan altı parçadan oluşur:\n\n- **SmartLine.Firmware**, ESP32 sensör okuma, MQTT bağlantısı ve yerel acil-durum latch davranışını yürütür.\n- **SmartLine.Cam**, ESP32-CAM üzerinden MJPEG görüntü akışı sağlar.\n- **SmartLine.Vision**, OpenCV ve kod okuyucularla fiziksel ürün bilgisini MQTT mesajına dönüştürür.\n- **SmartLine.API**, Minimal API, EF Core, PostgreSQL, MQTT tüketicileri ve SignalR hub'ını içerir.\n- **SmartLine.Dashboard**, rol bazlı operasyon, canlı veri ve alarm deneyimini React ile sunar.\n- **Altyapı katmanı**, Mosquitto, sertifikalar, Caddy ve Compose yapılandırmasıyla servis sınırlarını korur.\n\nBu tasarımda kullanıcı kimliği ile cihaz kimliği ayrıdır. İnsan kullanıcılar Viewer, Operator ve Admin rollerine sahipken cihazlar ayrı bir Device kimlik modeliyle mesajlaşır.\n\n## Öne çıkan teknik kararlar\n\nMQTT tarafında anonim veya düz metin bağlantı yerine karşılıklı TLS, kullanıcı bilgisi ve principal tabanlı ACL kullanılır. Böylece broker'a bağlanabilmek, belirli bir topic üzerinde işlem yapabilmek anlamına gelmez.\n\nKonveyör davranışı normal durdurma, acil durdurma ve yönetici resetini ayrı geçişler olarak ele alır. Firmware; cihaz epoch'u, yangın üretimi ve reset üretimini NVS üzerinde kalıcı tutar. Eksik veya bozuk kayıtla açılan bir cihaz güvenli tarafta kalır; yönetici reseti konveyörü otomatik başlatmaz.\n\nAPI tarafında ağır bir soyutlama katmanı yerine feature odaklı Minimal API yaklaşımı kullanılır. MQTT komut geçişleri tek aktif API örneği varsayımı altında bir kapı mekanizmasıyla sıralanır; bu, iş kurallarının endpoint'lere dağılmasını önler.\n\n## Doğrulama ve sınırlar\n\nAPI için gerçek PostgreSQL Testcontainers kullanan entegrasyon testleri, dashboard için lint ve build kontrolleri, firmware için PlatformIO derlemeleri ve Vision için Python doğrulamaları bulunur. Bu katmanlar ayrı çalıştığı için iletişim sözleşmelerindeki değişiklikler daha erken yakalanır.\n\nBu bir güvenlik-kritik donanım sertifikasyonu değildir. Servo konumunun güvenli kabul edilmesi motor enerjisini fiziksel olarak kesmez; gerçek bir üretim hattında hardwired acil durdurma devresi ve safety relay gerekir. Ayrıca komut geçişi koordinasyonu tek API instance varsayar; yatay ölçekleme için dağıtık bir güvenlik modeli gerekir.",
          "contentFormat": "markdown",
          "publishedAt": "2026-09-12T00:00:00.000Z",
          "updatedAt": "2026-09-12T00:00:00.000Z",
          "kind": "project",
          "description": "ESP32 cihazları, görüntü işleme ve gerçek zamanlı operatör arayüzünü bir araya getiren IIoT üretim hattı prototipi.",
          "repositoryUrl": "https://github.com/pitonmert/smart-line",
          "technologies": [
            "ASP.NET Core",
            "React",
            "PostgreSQL",
            "MQTT",
            "SignalR",
            "ESP32",
            "Python",
            "Docker"
          ],
          "categories": [
            "IIoT",
            "Gömülü Sistemler"
          ],
          "createdAt": "2026-02-17T10:07:55.000Z",
          "featured": true,
          "featuredOrder": 1
        },
        {
          "id": "project:media-stow",
          "locale": "tr",
          "title": "media-stow",
          "canonicalUrl": "https://www.pitonmert.com/projects/media-stow",
          "content": "## Bağlam ve amaç\n\nmedia-stow, fotoğraf, video ve ses dosyalarından oluşan büyük yerel arşivleri düzenlemek için hazırlanmış bir komut satırı aracıdır. Dosya adına veya klasör konumuna güvenmek yerine dosya içeriğini karşılaştırır; böylece aynı içeriğin farklı adla kopyalanması gibi durumları daha güvenilir biçimde ele alır.\n\nAraç, arşivleme görevini yalnız kopyalama olarak görmez. Hedef yapının doğrulanması, yinelenenlerin bulunması, iki dizin arasındaki ortak dosyaların ayrılması ve önceki işlemlerin karşılaştırılması da aynı çalışma alanının parçasıdır.\n\n## Nasıl çalışır\n\nBaşlangıç eşitlemesi kaynak dosyaları kategori ve uzantı temelli hedef yapısına yerleştirir; işlem sonunda mevcut durumu anlatan JSON indeksini oluşturur. Sonraki eşitlemeler, disk ile bu indeksi karşılaştırarak yeni, kayıp veya yanlış konumlanmış dosyaları belirler.\n\nDedup komutu eşit SHA-256 hash'ine sahip dosyaları gruplar. Verify komutu sıralanmış arşivin yapısını ve istenirse kaynakla eşleşmesini inceler; rapor üretir. Extract-common ve compare komutları ise iki farklı dizin veya işlem kaydı arasındaki ortak ya da değişen içerikleri analiz etmeye yarar.\n\n## Sistem tasarımı\n\nTek bir .NET konsol uygulaması; komut seçimini CommandRegistry üzerinden yapar. Her komut kendi iş akışını taşırken hash hesaplama, dosya erişimi, terminal günlüğü ve kullanıcı onayı ortak servisler olarak kalır.\n\nModeller; medya indeksini, dosya kaydını, özetleri ve doğrulama raporlarını temsil eder. Kategori ve filtre yapılandırmaları, hangi dosyaların hangi yapıya gireceğini merkezi biçimde tanımlar. Bu yaklaşım, komutların aynı dosya kurallarını ayrı ayrı yorumlamasını önler.\n\n## Öne çıkan teknik kararlar\n\nHashService, dosyayı akış üzerinden okuyarak SHA-256 hesaplar. Bu nedenle eşitlik dosya adı, tarih veya boyuttan çıkarılmaz; iki dosyanın bayt düzeyinde aynı içeriği taşıdığı gösterilir. Büyük arşivlerde maliyetli olabilen bu işlem, paralelliği işlemci kapasitesiyle sınırlandırarak dengelenir.\n\nİndeks ve raporlar önce geçici hedefe yazılıp sonra son ada taşınır. Bu, yarım yazılmış JSON dosyasının sonraki bir eşitlemede geçerli durum gibi okunma riskini azaltır. Değişiklik yapan komutlar kullanıcıdan açık onay ister; sync ve dedup için önizleme seçeneği bulunur.\n\n## Doğrulama ve sınırlar\n\nAraç; erişilemeyen dosyaları kontrol eden health check, yapı doğrulaması ve JSON tabanlı raporlarla operasyonel görünürlük sağlar. Ancak depoda ayrı bir otomatik test projesi bulunmadığı için dosya taşıma veya silme davranışının her senaryoda güvenli olduğuna dair otomatik kanıt yoktur.\n\nEşit hash, görsel olarak benzer dosya anlamına gelmez. Yeniden kodlanmış veya düzenlenmiş bir fotoğraf farklı dosya kabul edilir. Hash hesaplama bütün dosyayı okumayı gerektirir; çok büyük arşivlerde disk erişimi ve süre maliyeti beklenmelidir. Gerçek medya üzerinde çalışmadan önce önizleme ve yedekleme kullanmak gerekir.",
          "contentFormat": "markdown",
          "publishedAt": "2026-09-12T00:00:00.000Z",
          "updatedAt": "2026-09-12T00:00:00.000Z",
          "kind": "project",
          "description": "Medya arşivlerini içerik hash'leriyle karşılaştıran; eşitleme, doğrulama ve yinelenen dosya yönetimi için tasarlanmış .NET CLI aracı.",
          "repositoryUrl": "https://github.com/pitonmert/media-stow",
          "technologies": [
            "C#",
            ".NET",
            "SHA-256",
            "JSON"
          ],
          "categories": [
            "Geliştirici Aracı",
            "CLI"
          ],
          "createdAt": "2026-01-04T11:40:41.000Z",
          "featured": false
        },
        {
          "id": "project:git-sync",
          "locale": "tr",
          "title": "git-sync",
          "canonicalUrl": "https://www.pitonmert.com/projects/git-sync",
          "content": "## Bağlam ve amaç\n\ngit-sync, çok sayıda GitHub deposunu aynı yerel dizinde düzenli tutma ihtiyacından doğan bir komut satırı aracıdır. Eksik depoları bulma, güncel olmayan dalları güvenle ilerletme ve ortak Git yapılandırmalarını toplama işlerini tek bir akışta birleştirir.\n\nAraç, her depoyu zorla aynı noktaya getirmeyi amaçlamaz. Yerel çalışma, ayrışmış tarihçe ve bozuk depo gibi durumları görünür kılar; veri kaybı veya otomatik merge riski taşıyan kararları kullanıcı onayı olmadan vermez.\n\n## Nasıl çalışır\n\nÇalışma dört adımda ilerler. İlk olarak hedef dizindeki depolar denetlenir ve bozuk olanlar kullanıcıya raporlanır. Ardından GitHub CLI üzerinden erişilebilen ancak yerelde eksik olan depolar klonlanır. Güncelleme adımı uygun branch'leri fast-forward ile ilerletir; son adım ise depolardaki gitignore ve gitattributes kurallarını ortak çıktılarda toplar.\n\nAkış başlamadan Git, GitHub CLI, oturum durumu ve hedef dizin doğrulanır. İstenirse dry-run ile hiçbir dosya değiştirmeden planlanan silme, klonlama, güncelleme ve birleştirme işlemleri görülür. Sonuçlar terminalde özetlenebilir veya JSON olarak dışa aktarılabilir.\n\n## Sistem tasarımı\n\nUygulama Core, Application ve Infrastructure katmanlarına ayrılmıştır. Core; seçenekleri, sonuç modellerini ve servis sözleşmelerini taşır. Application; orkestratörü, doğrulayıcıları ve iş akışı adımlarını içerir. Infrastructure ise Git, GitHub CLI, dosya sistemi, süreç çalıştırma ve Spectre.Console uygulamalarını sağlar.\n\nBu ayrım, Git komutlarını ya da dosya erişimini doğrudan iş kurallarına bağlamaz. Testler; süreç, dosya sistemi ve kullanıcı arayüzünü mock ederek iş akışı kararlarını gerçek bir GitHub hesabına veya çalışma dizinine ihtiyaç duymadan sınar.\n\n## Öne çıkan teknik kararlar\n\nAktif takip dalı yalnız fast-forward mümkünse güncellenir. Diğer branch'lerde önce atalık ilişkisi denetlenir; ayrışmış tarihçeler otomatik merge edilmez. Bu tercih, kolaylık karşılığında kullanıcının yerel çalışmasını kaybetme riskini kabul etmez.\n\nKlonlama ve güncelleme paralel yürütülebilir ancak eşzamanlılık sınırlandırılır. Böylece büyük repo kümelerinde ağ ve disk kullanımı kontrol altında tutulur. Alt modüller, ilgili depo güncellendiğinde ayrı ele alınır.\n\nBozuk depo temizliği ve dosya üreten birleştirme adımları dry-run ile görünür hale gelir. Bu araçta önizleme modu yalnız bir raporlama seçeneği değil, potansiyel olarak yıkıcı bir iş akışının güvenlik sınırıdır.\n\n## Doğrulama ve sınırlar\n\nBirim testleri; doğrulama, bozuk depo tespiti, klonlama, güncelleme, dry-run ve yapılandırma birleştirme adımlarını kapsar. Süreç sonuçları ve zaman aşımı davranışı da soyutlama katmanında kontrol edilebilir.\n\nAraç genel amaçlı bir merge conflict çözücüsü değildir. Yerel değişiklikler, detached HEAD, yetki sorunları veya ayrışmış branch'ler otomatik olarak çözülmez. Çalışması için Git, GitHub CLI ve doğrulanmış bir GitHub oturumu gerekir; yükseltilmiş yetkilerle çalıştırılmamalıdır.",
          "contentFormat": "markdown",
          "publishedAt": "2026-09-12T00:00:00.000Z",
          "updatedAt": "2026-09-12T00:00:00.000Z",
          "kind": "project",
          "description": "Birden fazla GitHub deposunu tek komutta denetleyen, güvenli güncelleyen ve ortak Git yapılandırmalarını birleştiren .NET CLI aracı.",
          "repositoryUrl": "https://github.com/pitonmert/git-sync",
          "technologies": [
            "C#",
            ".NET",
            "Git",
            "GitHub CLI",
            "Spectre.Console"
          ],
          "categories": [
            "Geliştirici Aracı",
            "CLI"
          ],
          "createdAt": "2026-01-04T11:38:56.000Z",
          "featured": false
        }
      ],
      "writing": [
        {
          "id": "writing:sql-quick-reference",
          "locale": "tr",
          "title": "SQL nedir?",
          "canonicalUrl": "https://www.pitonmert.com/writing/sql-quick-reference",
          "content": "# SQL nedir?\n\nSQL (*Structured Query Language*), ilişkisel veritabanlarında veriyi tanımlamak,\nsorgulamak ve değiştirmek için kullanılan standart dildir. Tablolar ve aralarındaki\nilişkiler üzerinde çalışır; kayıtları filtreleme, birleştirme, özetleme, ekleme,\ngüncelleme ve silme gibi işlemleri ifade eder. Bu sayfa, temel SQL komutlarını\nPostgreSQL örnekleriyle hızlıca hatırlamak için hazırlanmıştır.\n\n***\n\n## İçindekiler / Hızlı Bakış\n\n**DQL:** [SELECT](https://www.pitonmert.com/writing/sql-quick-reference#select) · [DISTINCT](https://www.pitonmert.com/writing/sql-quick-reference#distinct) · [WHERE](https://www.pitonmert.com/writing/sql-quick-reference#where) · [ORDER BY](https://www.pitonmert.com/writing/sql-quick-reference#order-by) · [LIMIT / OFFSET](https://www.pitonmert.com/writing/sql-quick-reference#limit-offset)\n\n**İlişkisel / Gelişmiş:** [JOIN](https://www.pitonmert.com/writing/sql-quick-reference#join) · [Alt sorgu (SUBQUERY)](https://www.pitonmert.com/writing/sql-quick-reference#subquery) · [CTE (WITH)](https://www.pitonmert.com/writing/sql-quick-reference#cte-with) · [CASE WHEN](https://www.pitonmert.com/writing/sql-quick-reference#case-when) · [UNION / UNION ALL](https://www.pitonmert.com/writing/sql-quick-reference#union-all) · [INTERSECT / EXCEPT](https://www.pitonmert.com/writing/sql-quick-reference#intersect-except) · [Toplama fonksiyonları](https://www.pitonmert.com/writing/sql-quick-reference#aggregate-functions) · [GROUP BY / HAVING](https://www.pitonmert.com/writing/sql-quick-reference#group-by-having) · [Pencere fonksiyonları](https://www.pitonmert.com/writing/sql-quick-reference#window-functions)\n\n**DML:** [INSERT](https://www.pitonmert.com/writing/sql-quick-reference#insert) · [UPDATE](https://www.pitonmert.com/writing/sql-quick-reference#update) · [DELETE](https://www.pitonmert.com/writing/sql-quick-reference#delete)\n\n**DDL:** [CREATE](https://www.pitonmert.com/writing/sql-quick-reference#create) · [ALTER](https://www.pitonmert.com/writing/sql-quick-reference#alter) · [DROP](https://www.pitonmert.com/writing/sql-quick-reference#drop) · [TRUNCATE](https://www.pitonmert.com/writing/sql-quick-reference#truncate) · [PRIMARY KEY / FOREIGN KEY](https://www.pitonmert.com/writing/sql-quick-reference#keys) · [CHECK / DEFAULT / NOT NULL](https://www.pitonmert.com/writing/sql-quick-reference#constraints) · [VIEW](https://www.pitonmert.com/writing/sql-quick-reference#view) · [INDEX](https://www.pitonmert.com/writing/sql-quick-reference#index) · [TRIGGER](https://www.pitonmert.com/writing/sql-quick-reference#trigger)\n\n**TCL:** [BEGIN / COMMIT / ROLLBACK](https://www.pitonmert.com/writing/sql-quick-reference#transaction)\n\n**DCL:** [GRANT / REVOKE](https://www.pitonmert.com/writing/sql-quick-reference#permissions)\n\n***\n\n## DQL — Veri Sorgulama\n\n*Data Query Language: veriyi okumaya yönelik ifadeler.*\n\n### SELECT\n\nDöndürülecek sütunları belirler. Sütuna veya tabloya geçici bir ad vermek için `AS` kullanılır (alias).\n\n**Veri:**\n\n```text\nmusteriler(id INTEGER, ad TEXT)\n1 | Ayşe\n2 | Mehmet\n```\n\n**Sorgu:**\n\n```sql\nSELECT ad\nFROM musteriler\nORDER BY id;\n```\n\n**Sonuç:**\n\n```text\nad\nAyşe\nMehmet\n```\n\n*Alias örneği:*\n\n```sql\nSELECT m.ad AS musteri_adi FROM musteriler AS m;\n-- Sonuç: musteri_adi → Ayşe\n```\n\n### DISTINCT\n\nSeçilen alanların oluşturduğu tekrar eden sonuç satırlarını kaldırır.\n\n**Veri:**\n\n```text\nmusteriler(id INTEGER, sehir TEXT)\n1 | Ankara\n2 | Ankara\n3 | İstanbul\n```\n\n**Sorgu:**\n\n```sql\nSELECT DISTINCT sehir\nFROM musteriler\nORDER BY sehir;\n```\n\n**Sonuç:**\n\n```text\nsehir\nAnkara\nİstanbul\n```\n\n### WHERE\n\nKoşula uyan satırları seçer.\n\n**Veri:**\n\n```text\nmusteriler(id INTEGER, ad TEXT, sehir TEXT)\n1 | Ayşe   | İstanbul\n2 | Mehmet | Ankara\n```\n\n**Sorgu:**\n\n```sql\nSELECT ad\nFROM musteriler\nWHERE sehir = 'İstanbul';\n```\n\n**Sonuç:**\n\n```text\nad\nAyşe\n```\n\n### ORDER BY\n\nSonuçları artan (`ASC`) veya azalan (`DESC`) sıraya dizer.\n\n**Veri:**\n\n```text\nsiparisler(id INTEGER, tutar INTEGER)\n1 | 300\n2 | 900\n```\n\n**Sorgu:**\n\n```sql\nSELECT id, tutar\nFROM siparisler\nORDER BY tutar DESC, id;\n```\n\n**Sonuç:**\n\n```text\nid | tutar\n2  | 900\n1  | 300\n```\n\n### LIMIT / OFFSET\n\nSonuç sayısını sınırlar ve başlangıçtan satır atlar.\n\n**Veri:**\n\n```text\nsiparisler(id INTEGER, tutar INTEGER)\n1 | 300\n2 | 900\n3 | 600\n```\n\n**Sorgu:**\n\n```sql\nSELECT id, tutar\nFROM siparisler\nORDER BY id\nLIMIT 2 OFFSET 1;\n```\n\n**Sonuç:**\n\n```text\nid | tutar\n2  | 900\n3  | 600\n```\n\n*Not: İlk satır atlanır. Kararlı sayfalama için benzersiz bir sıralama kullan.*\n\n***\n\n## İlişkiler ve Gelişmiş Sorgular\n\n*Tabloları birleştirme, ara sonuç üretme ve hesaplama yapıları.*\n\n### JOIN\n\nTabloları belirtilen koşula göre birleştirir.\n\n**Veri:**\n\n```text\nmusteriler(id INTEGER, ad TEXT)\n1 | Ayşe\n2 | Mehmet\n\nsiparisler(id INTEGER, musteri_id INTEGER)\n10 | 1\n```\n\n**Sorgu:**\n\n```sql\nSELECT m.ad, s.id AS siparis_no\nFROM musteriler AS m\nLEFT JOIN siparisler AS s ON s.musteri_id = m.id\nORDER BY m.id;\n```\n\n**Sonuç:**\n\n```text\nad     | siparis_no\nAyşe   | 10\nMehmet | NULL\n```\n\n*Not: `LEFT JOIN` soldaki eşleşmeyen satırları da korur; `INNER JOIN` yalnızca eşleşenleri getirir.*\n\n### Alt sorgu (SUBQUERY)\n\nBir sorgunun sonucunu başka bir sorgunun içinde kullanır.\n\n**Veri:**\n\n```text\nsiparisler(id INTEGER, tutar INTEGER)\n1 | 300\n2 | 900\n```\n\n**Sorgu:**\n\n```sql\nSELECT id, tutar\nFROM siparisler\nWHERE tutar > (SELECT AVG(tutar) FROM siparisler);\n```\n\n**Sonuç:**\n\n```text\nid | tutar\n2  | 900\n```\n\n*Not: Ortalama 600'dür; yalnızca ortalamanın üzerindeki sipariş gelir.*\n\n### CTE (WITH)\n\nSorgu süresince kullanılacak bir ara sonucu isimlendirir.\n\n**Veri:**\n\n```text\nsiparisler(id INTEGER, musteri_id INTEGER, tutar INTEGER)\n1 | 1 | 300\n2 | 1 | 900\n3 | 2 | 200\n```\n\n**Sorgu:**\n\n```sql\nWITH toplamlar AS (\n  SELECT musteri_id, SUM(tutar) AS toplam\n  FROM siparisler\n  GROUP BY musteri_id\n)\nSELECT musteri_id, toplam\nFROM toplamlar\nWHERE toplam > 1000;\n```\n\n**Sonuç:**\n\n```text\nmusteri_id | toplam\n1          | 1200\n```\n\n### CASE WHEN\n\nKoşula göre bir sonuç değeri üretir.\n\n**Veri:**\n\n```text\nsiparisler(id INTEGER, tutar INTEGER)\n1 | 300\n2 | 900\n```\n\n**Sorgu:**\n\n```sql\nSELECT id,\n  CASE WHEN tutar >= 500 THEN 'Yüksek'\n       ELSE 'Standart' END AS seviye\nFROM siparisler\nORDER BY id;\n```\n\n**Sonuç:**\n\n```text\nid | seviye\n1  | Standart\n2  | Yüksek\n```\n\n### UNION / UNION ALL\n\nUyumlu sorgu sonuçlarını alt alta birleştirir.\n\n**Veri:**\n\n```text\naktif_musteriler(id INTEGER): 1, 2\neski_musteriler(id INTEGER): 2, 3\n```\n\n**Sorgu:**\n\n```sql\nSELECT id FROM aktif_musteriler\nUNION\nSELECT id FROM eski_musteriler\nORDER BY id;\n```\n\n**Sonuç:**\n\n```text\nid\n1\n2\n3\n```\n\n*Not: `UNION` tekrarları kaldırır. Aynı sorguda `UNION ALL` kullanılırsa sonuç 1, 2, 2, 3 olur.*\n\n### INTERSECT / EXCEPT\n\nOrtak satırları veya ilk kümede olup ikincide olmayanları bulur.\n\n**Veri:**\n\n```text\naktif_musteriler(id INTEGER): 1, 2\neski_musteriler(id INTEGER): 2, 3\n```\n\n**Sorgu:**\n\n```sql\nSELECT id FROM aktif_musteriler\nINTERSECT\nSELECT id FROM eski_musteriler;\n```\n\n**Sonuç:**\n\n```text\nid\n2\n```\n\n*Not: `INTERSECT` yerine `EXCEPT` kullanılırsa sonuç 1 olur.*\n\n### Toplama fonksiyonları (COUNT / SUM / AVG / MIN / MAX)\n\nBirden fazla satırdan tek bir özet sonuç üretir.\n\n**Veri:**\n\n```text\nsiparisler(id INTEGER, tutar INTEGER)\n1 | 300\n2 | 900\n```\n\n**Sorgu:**\n\n```sql\nSELECT COUNT(*) AS adet,\n       SUM(tutar) AS toplam,\n       AVG(tutar) AS ortalama\nFROM siparisler;\n```\n\n**Sonuç:**\n\n```text\nadet | toplam | ortalama\n2    | 1200   | 600\n```\n\n*Not: Ortalamanın ondalık gösterimi istemciye göre değişebilir. `MIN` ve `MAX` en küçük ve en büyük değeri bulur.*\n\n### GROUP BY / HAVING\n\n`GROUP BY` aynı değere sahip satırları gruplar; `HAVING` gruplama sonrasında grupları filtreler (`WHERE` satırları, `HAVING` grupları filtreler).\n\n**Veri:**\n\n```text\nsiparisler(id INTEGER, musteri_id INTEGER)\n1 | 1\n2 | 1\n3 | 2\n```\n\n**Sorgu (GROUP BY):**\n\n```sql\nSELECT musteri_id, COUNT(*) AS adet\nFROM siparisler\nGROUP BY musteri_id\nORDER BY musteri_id;\n```\n\n**Sonuç:**\n\n```text\nmusteri_id | adet\n1          | 2\n2          | 1\n```\n\n**Sorgu (HAVING ile filtreleme):**\n\n```sql\nSELECT musteri_id, COUNT(*) AS adet\nFROM siparisler\nGROUP BY musteri_id\nHAVING COUNT(*) >= 2;\n```\n\n**Sonuç:**\n\n```text\nmusteri_id | adet\n1          | 2\n```\n\n### Pencere fonksiyonları (OVER / PARTITION BY)\n\nSatırları koruyarak sıra veya kümülatif toplam hesaplar.\n\n**Veri:**\n\n```text\nsiparisler(id INTEGER, musteri_id INTEGER, tutar INTEGER)\n1 | 1 | 300\n2 | 1 | 900\n3 | 2 | 200\n```\n\n**Sorgu:**\n\n```sql\nSELECT id, musteri_id,\n  SUM(tutar) OVER (\n    PARTITION BY musteri_id ORDER BY id\n    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n  ) AS biriken_tutar\nFROM siparisler\nORDER BY id;\n```\n\n**Sonuç:**\n\n```text\nid | musteri_id | biriken_tutar\n1  | 1          | 300\n2  | 1          | 1200\n3  | 2          | 200\n```\n\n***\n\n## DML — Veri Değiştirme\n\n*Data Manipulation Language: kayıt ekleme, değiştirme ve silme.*\n\n### INSERT\n\nTabloya yeni kayıt ekler. `RETURNING`, eklenen satırın belirtilen alanlarını döndürür.\n\n**Veri:**\n\n```text\nmusteriler(id INTEGER PRIMARY KEY, ad TEXT)\nBaşlangıçta boş.\n```\n\n**Sorgu:**\n\n```sql\nINSERT INTO musteriler (id, ad)\nVALUES (1, 'Ayşe')\nRETURNING id, ad;\n```\n\n**Sonuç:**\n\n```text\nid | ad\n1  | Ayşe\n```\n\n### UPDATE\n\nKoşula uyan kayıtların belirtilen alanlarını değiştirir.\n\n**Veri:**\n\n```text\nmusteriler(id INTEGER PRIMARY KEY, ad TEXT, sehir TEXT)\n1 | Ayşe | İstanbul\n```\n\n**Sorgu:**\n\n```sql\nUPDATE musteriler\nSET sehir = 'Ankara'\nWHERE id = 1\nRETURNING id, sehir;\n```\n\n**Sonuç:**\n\n```text\nid | sehir\n1  | Ankara\n```\n\n*Not: `WHERE` kullanılmazsa tüm satırlar güncellenir.*\n\n### DELETE\n\nKoşula uyan satırları siler; tablo yapısını korur.\n\n**Veri:**\n\n```text\nsiparisler(id INTEGER PRIMARY KEY, tutar INTEGER)\n1 | 300\n2 | 900\n```\n\n**Sorgu:**\n\n```sql\nDELETE FROM siparisler\nWHERE id = 2\nRETURNING id, tutar;\n```\n\n**Sonuç:**\n\n```text\nid | tutar\n2  | 900\n```\n\n*Not: Çıktı silinen satırdır; tabloda yalnızca 1 numaralı sipariş kalır. `WHERE` olmazsa tüm satırlar silinir.*\n\n***\n\n## DDL — Yapı Tanımlama\n\n*Data Definition Language: tablo ve diğer veritabanı nesnelerini yönetir.*\n\n### CREATE\n\nYeni bir veritabanı nesnesi oluşturur.\n\n**Veri:** `kategoriler` tablosu henüz yok. Hedef: `kategoriler(id INTEGER PRIMARY KEY, ad TEXT NOT NULL)`\n\n**Sorgu:**\n\n```sql\nCREATE TABLE kategoriler (\n  id INTEGER PRIMARY KEY,\n  ad TEXT NOT NULL\n);\n```\n\n**Sonuç:**\n\n```text\nCREATE TABLE\n```\n\n*Not: Belirtilen sütunlarla boş bir tablo oluşur.*\n\n### ALTER\n\nMevcut tablonun yapısını değiştirir.\n\n**Veri:**\n\n```text\nmusteriler(id INTEGER PRIMARY KEY, ad TEXT)\n1 | Ayşe\n```\n\n**Sorgu:**\n\n```sql\nALTER TABLE musteriler ADD COLUMN telefon TEXT;\nSELECT id, ad, telefon FROM musteriler;\n```\n\n**Sonuç:**\n\n```text\nid | ad   | telefon\n1  | Ayşe | NULL\n```\n\n### DROP\n\nNesneyi verileriyle birlikte kaldırır.\n\n**Veri:**\n\n```text\nkategoriler(id INTEGER PRIMARY KEY, ad TEXT)\n1 | Kitap\nTabloya bağımlı başka nesne yok.\n```\n\n**Sorgu:**\n\n```sql\nDROP TABLE kategoriler;\n```\n\n**Sonuç:**\n\n```text\nDROP TABLE\n```\n\n*Not: Tablo artık yoktur; yeniden sorgulamak hata verir.*\n\n### TRUNCATE\n\nTablodaki tüm satırları kaldırır; tablo yapısını korur.\n\n**Veri:**\n\n```text\nsiparisler(id INTEGER PRIMARY KEY, tutar INTEGER)\n1 | 300\n2 | 900\nTabloyu referans alan foreign key yok.\n```\n\n**Sorgu:**\n\n```sql\nTRUNCATE TABLE siparisler;\nSELECT COUNT(*) AS kalan_satir FROM siparisler;\n```\n\n**Sonuç:**\n\n```text\nkalan_satir\n0\n```\n\n*Not: `WHERE` kabul etmez. PostgreSQL'de açık transaction içinde geri alınabilir.*\n\n### PRIMARY KEY / FOREIGN KEY\n\nPrimary key benzersiz kimlik sağlar; foreign key var olan bir kayda bağlar.\n\n**Veri:**\n\n```text\nmusteriler(id INTEGER PRIMARY KEY)\n1\n\nHedef: adresler(id INTEGER PRIMARY KEY,\n               musteri_id INTEGER NOT NULL REFERENCES musteriler(id))\n```\n\n**Sorgu:**\n\n```sql\nCREATE TABLE adresler (\n  id INTEGER PRIMARY KEY,\n  musteri_id INTEGER NOT NULL REFERENCES musteriler(id)\n);\nINSERT INTO adresler (id, musteri_id)\nVALUES (10, 1)\nRETURNING id, musteri_id;\n```\n\n**Sonuç:**\n\n```text\nid | musteri_id\n10 | 1\n```\n\n*Not: Olmayan bir müşteri kimliği foreign key hatası verir. Primary key tekrar veya `NULL` kabul etmez.*\n\n### CHECK / DEFAULT / NOT NULL\n\n`CHECK` koşulu sınar, `DEFAULT` değer sağlar, `NOT NULL` eksik değeri engeller.\n\n**Veri:**\n\n```text\nurunler tablosu henüz yok.\nHedef: urunler(id INTEGER PRIMARY KEY, fiyat INTEGER NOT NULL,\n              stok INTEGER NOT NULL DEFAULT 0)\nKural: fiyat ve stok negatif olamaz.\n```\n\n**Sorgu:**\n\n```sql\nCREATE TABLE urunler (\n  id INTEGER PRIMARY KEY,\n  fiyat INTEGER NOT NULL CHECK (fiyat >= 0),\n  stok INTEGER NOT NULL DEFAULT 0 CHECK (stok >= 0)\n);\nINSERT INTO urunler (id, fiyat)\nVALUES (1, 100)\nRETURNING id, fiyat, stok;\n```\n\n**Sonuç:**\n\n```text\nid | fiyat | stok\n1  | 100   | 0\n```\n\n*Not: `DEFAULT`, açıkça verilen `NULL` değerini değiştirmez.*\n\n### VIEW\n\nBir sorguyu sanal tablo adıyla tekrar kullanılabilir hale getirir.\n\n**Veri:**\n\n```text\nsiparisler(id INTEGER, durum TEXT)\n1 | gonderildi\n2 | hazirlaniyor\nView henüz yok.\n```\n\n**Sorgu:**\n\n```sql\nCREATE VIEW gonderilen_siparisler AS\nSELECT id FROM siparisler WHERE durum = 'gonderildi';\nSELECT id FROM gonderilen_siparisler;\n```\n\n**Sonuç:**\n\n```text\nid\n1\n```\n\n*Not: Normal view, sonuçların ayrı bir kopyasını saklamaz.*\n\n### INDEX\n\nBelirli arama ve birleştirme işlemlerini hızlandırabilecek bir dizin oluşturur.\n\n**Veri:**\n\n```text\nsiparisler(id INTEGER, musteri_id INTEGER)\n1 | 10\n2 | 20\nİndeks henüz yok.\n```\n\n**Sorgu:**\n\n```sql\nCREATE INDEX idx_siparisler_musteri\nON siparisler (musteri_id);\n```\n\n**Sonuç:**\n\n```text\nCREATE INDEX\n```\n\n*Not: Satırlar değişmez. İndeks disk ve yazma maliyeti getirir; kullanılacağını sorgu planlayıcısı belirler.*\n\n### TRIGGER\n\nBir veri olayında tanımlı işlemi otomatik çalıştırır.\n\n**Veri:**\n\n```text\nmusteriler(id INTEGER PRIMARY KEY, ad TEXT)\nBaşlangıçta boş; aşağıdaki fonksiyon ve trigger henüz yok.\n```\n\n**Sorgu:**\n\n```sql\nCREATE FUNCTION adi_temizle() RETURNS trigger\nLANGUAGE plpgsql AS $$\nBEGIN\n  NEW.ad := trim(NEW.ad);\n  RETURN NEW;\nEND;\n$$;\nCREATE TRIGGER musteri_adi_temizligi\nBEFORE INSERT ON musteriler\nFOR EACH ROW EXECUTE FUNCTION adi_temizle();\n\nINSERT INTO musteriler (id, ad)\nVALUES (1, '  Ayşe  ')\nRETURNING id, ad;\n```\n\n**Sonuç:**\n\n```text\nid | ad\n1  | Ayşe\n```\n\n*Not: PostgreSQL örneğidir. Adın başındaki ve sonundaki boşluklar kayıt eklenmeden temizlenir.*\n\n***\n\n## TCL — İşlem Yönetimi\n\n*Transaction Control Language: değişiklikleri bir transaction içinde birlikte yönetir.*\n\n### BEGIN / COMMIT / ROLLBACK\n\n`BEGIN` bir transaction başlatır. İçindeki değişiklikler `COMMIT` ile kalıcı hale gelir, `ROLLBACK` ile geri alınır.\n\n**Örnek 1 — COMMIT değişikliği kalıcılaştırır**\n\nVeri: `musteriler(id INTEGER PRIMARY KEY, sehir TEXT)` → `1 | İstanbul`\n\n```sql\nBEGIN;\nUPDATE musteriler SET sehir = 'Ankara' WHERE id = 1;\nCOMMIT;\nSELECT sehir FROM musteriler WHERE id = 1;\n```\n\nSonuç: `sehir → Ankara`\n\n*Not: Commit sonrasında `ROLLBACK` bu değişikliği geri almaz.*\n\n**Örnek 2 — ROLLBACK değişikliği geri alır**\n\nVeri: `siparisler(id INTEGER PRIMARY KEY, tutar INTEGER)` → `1 | 300`\n\n```sql\nBEGIN;\nDELETE FROM siparisler WHERE id = 1;\nROLLBACK;\nSELECT id, tutar FROM siparisler;\n```\n\nSonuç: `id: 1, tutar: 300` (kayıt hâlâ duruyor; silme işlenmedi)\n\n***\n\n## DCL — Yetki Yönetimi\n\n*Data Control Language: kullanıcı ve rollerin nesne erişimini yönetir.*\n\n### GRANT / REVOKE\n\n`GRANT` bir role nesne üzerinde yetki verir; `REVOKE` daha önce verilmiş bir yetkiyi kaldırır.\n\n**Veri:** `siparisler(id INTEGER, tutar INTEGER)`, rol: `raporlama_rolu` (oturum tablo sahibi; rolün başlangıçta okuma yetkisi yok).\n\n```sql\nGRANT SELECT ON siparisler TO raporlama_rolu;\n-- Sonuç: GRANT\n\nREVOKE SELECT ON siparisler FROM raporlama_rolu;\n-- Sonuç: REVOKE\n```\n\n*Not: `GRANT` sonrası bağlantı/şema erişimi ayrıca gerekebilir. `REVOKE` sonrası, başka rol üyeliklerinden veya `PUBLIC`'ten gelen erişim sürebilir.*\n\n***\n\n## Kısa Notlar\n\n- NULL kontrolü için `IS NULL` kullanılır; `NULL`, boş metin (`''`) ve sıfır (`0`) birbirinden farklıdır.\n- `COUNT(*)` tüm satırları sayar; `COUNT(sutun)` yalnızca o sütunda `NULL` olmayan değerleri sayar.\n- `ORDER BY` olmadan sonuç sırası garanti edilmez.\n- Kullanıcı girdisini SQL metnine doğrudan eklemek yerine sürücünün parametre bağlama özelliğini kullan (SQL injection riskini önler).\n\n## Kaynaklar\n\n- [PostgreSQL: SQL komutları](https://www.postgresql.org/docs/current/sql-commands.html)\n- [PostgreSQL: transaction](https://www.postgresql.org/docs/current/tutorial-transactions.html)",
          "contentFormat": "markdown",
          "publishedAt": "2026-09-13T00:00:00.000Z",
          "updatedAt": "2026-09-14T00:00:00.000Z",
          "kind": "writing",
          "description": "İlişkisel veritabanlarında veriyi sorgulamak, değiştirmek ve tablo yapılarını yönetmek için kullanılan dil.",
          "tags": [
            "SQL",
            "PostgreSQL",
            "Referans"
          ]
        }
      ]
    },
    "en": {
      "canonicalUrl": "https://www.pitonmert.com/en",
      "about": {
        "id": "about:mert-atci",
        "locale": "en",
        "title": "Mert Atcı",
        "canonicalUrl": "https://www.pitonmert.com/en",
        "content": "# Mert Atcı\n\nFull Stack .NET Developer\n\nI build web applications with ASP.NET Core and React. My projects address needs such as vocabulary learning, portfolio tracking, and production line monitoring, and I manage the entire process, from data modeling and user interfaces to testing and deployment. My main focus is backend development; here I share how these systems work and the technical decisions behind them.\n\n[Email](mailto:mertatci@pitonmert.com) · [Download CV](https://www.pitonmert.com/cv/mert-atci-cv.pdf)\n\n[GitHub](https://github.com/pitonmert) · [LinkedIn](https://linkedin.com/in/pitonmert)\n\n## Technical Skills\n\n### Backend & Data\n\nC#, ASP.NET Core, EF Core, PostgreSQL, ASP.NET Core Identity\n\n### Frontend\n\nReact, TypeScript, Tailwind CSS\n\n### Testing & Quality\n\nxUnit, Testcontainers, Vitest\n\n### Deployment & Integration\n\nDocker, nginx, GitHub Actions, Cloudflare Tunnel, Cloudflare Access, SignalR, MQTT\n\n## Education\n\n### KTO Karatay University\n\nComputer Programming · Associate Degree\n\nKonya · September 2024 – June 2026\n\nGPA: 3.01 / 4.00",
        "contentFormat": "markdown",
        "kind": "about",
        "person": {
          "name": "Mert Atcı",
          "role": "Full Stack .NET Developer",
          "summary": "I build web applications with ASP.NET Core and React. My projects address needs such as vocabulary learning, portfolio tracking, and production line monitoring, and I manage the entire process, from data modeling and user interfaces to testing and deployment. My main focus is backend development; here I share how these systems work and the technical decisions behind them.",
          "email": "mertatci@pitonmert.com",
          "cvUrl": "https://www.pitonmert.com/cv/mert-atci-cv.pdf"
        },
        "socialLinks": [
          {
            "label": "GitHub",
            "url": "https://github.com/pitonmert"
          },
          {
            "label": "LinkedIn",
            "url": "https://linkedin.com/in/pitonmert"
          }
        ],
        "skills": [
          {
            "id": "backend",
            "title": "Backend & Data",
            "technologies": [
              "C#",
              "ASP.NET Core",
              "EF Core",
              "PostgreSQL",
              "ASP.NET Core Identity"
            ]
          },
          {
            "id": "frontend",
            "title": "Frontend",
            "technologies": [
              "React",
              "TypeScript",
              "Tailwind CSS"
            ]
          },
          {
            "id": "testing",
            "title": "Testing & Quality",
            "technologies": [
              "xUnit",
              "Testcontainers",
              "Vitest"
            ]
          },
          {
            "id": "deployment",
            "title": "Deployment & Integration",
            "technologies": [
              "Docker",
              "nginx",
              "GitHub Actions",
              "Cloudflare Tunnel",
              "Cloudflare Access",
              "SignalR",
              "MQTT"
            ]
          }
        ],
        "education": [
          {
            "id": "education:kto-karatay",
            "school": "KTO Karatay University",
            "degree": "Computer Programming · Associate Degree",
            "location": "Konya",
            "period": "September 2024 – June 2026",
            "grade": "GPA: 3.01 / 4.00"
          }
        ]
      },
      "experience": [
        {
          "id": "experience:ae-yazilim-internship",
          "locale": "en",
          "title": "Intern · AE Yazılım",
          "canonicalUrl": "https://www.pitonmert.com/en#experience",
          "content": "# Intern · AE Yazılım\n\nKonya · June – August 2025\n\nI developed LetterHop, a word game for children, using Unity and C#. I worked on dynamic content from a REST API, text-to-speech, persistent progress tracking, and AdMob integration.",
          "contentFormat": "markdown",
          "kind": "experience",
          "role": "Intern",
          "company": "AE Yazılım",
          "location": "Konya",
          "period": "June – August 2025",
          "description": "I developed LetterHop, a word game for children, using Unity and C#. I worked on dynamic content from a REST API, text-to-speech, persistent progress tracking, and AdMob integration."
        }
      ],
      "projects": [
        {
          "id": "project:release-radar",
          "locale": "en",
          "title": "release-radar",
          "canonicalUrl": "https://www.pitonmert.com/en/projects/release-radar",
          "content": "## Context and purpose\n\nrelease-radar collects release notes from the tools it follows into one notification stream instead of requiring manual checks across many sites. It does more than discover a release: it summarises lengthy or fragmented notes in Turkish and delivers a Telegram notification that keeps a route back to the source.\n\nThe project is a long-running service that manages its own loop rather than a one-off scheduled job. Scan frequency, notification history, and retry behaviour stay inside the application.\n\n## How it works\n\nOn startup, the service reads its source configuration and runs an initial scan. When it sees a source for the first time, it stores current entries as a baseline without sending notifications. Later scans process only entries that have not been seen before.\n\nFor a new release, the service retrieves the RSS or Atom entry for the relevant source type; VS Code has a dedicated route that retrieves detailed Markdown notes. Gemini produces a Turkish summary, the message is split if it exceeds Telegram limits, and the entry is persisted only after successful delivery.\n\n## System design\n\nThe Python application brings together source loading, network retrieval, summarisation, and notification in its main scan loop. SQLite stores processed releases through a unique source-and-entry identifier.\n\nThe configuration file describes monitored sources and their types. Docker Compose mounts that configuration read-only while keeping the database in a persistent runtime directory. The source list can therefore change without turning notification history into repository content.\n\n## Key technical decisions\n\nCreating a silent baseline on first scan prevents a flood of old releases. The composite source and guid key in SQLite prevents the same entry from being sent again after a container restart.\n\nExternal network requests, Gemini calls, and Telegram delivery can fail temporarily. Requests use retry and backoff behaviour, and a release is not marked as seen until Telegram confirms delivery. Splitting long messages prevents one oversized release note from disappearing because of platform limits.\n\nThe service handles shutdown signals and uses interruptible sleep in its waiting loop, making Compose restarts more predictable between scans.\n\n## Validation and limits\n\nPytest coverage includes the SQLite schema, first-run baselining, duplicate prevention, and the rule that failed Telegram delivery must not mark an entry as processed. Ruff and Docker build checks are also part of continuous integration.\n\nSummary accuracy depends on the source text and language-model output. Telegram delivery and the SQLite update are not one atomic operation, so an interruption between them can lead to a duplicate notification during a later scan. Removing persistent runtime data also rebuilds the baseline for each source.",
          "contentFormat": "markdown",
          "publishedAt": "2026-09-12T00:00:00.000Z",
          "updatedAt": "2026-09-12T00:00:00.000Z",
          "kind": "project",
          "description": "A persistent automation service that monitors software release notes, produces Turkish summaries, and delivers them through Telegram.",
          "repositoryUrl": "https://github.com/pitonmert/release-radar",
          "technologies": [
            "Python",
            "SQLite",
            "Gemini API",
            "Telegram Bot API",
            "Docker"
          ],
          "categories": [
            "Automation",
            "Service"
          ],
          "createdAt": "2026-05-17T13:03:04.000Z",
          "featured": false
        },
        {
          "id": "project:portfolio-tracker",
          "locale": "en",
          "title": "portfolio-tracker",
          "canonicalUrl": "https://www.pitonmert.com/en/projects/portfolio-tracker",
          "content": "## Context and purpose\n\nportfolio-tracker was built to bring assets from different sources and cash balances into one personal workspace. The application does not derive the portfolio from prices alone: it retains buy and sell activity, then calculates open and closed positions from that history.\n\nIts purpose is not to provide financial advice. Instead, it makes the portfolio data model visible and reviewable by separating transaction history, price sourcing, calculation, and presentation.\n\n## How it works\n\nWhen a user adds a buy or sell for an asset, the entry is first written to the transaction ledger. The API combines that history with the latest price to calculate quantity, cost basis, realised profit or loss, and unrealised profit or loss.\n\nThe dashboard reads open positions, closed positions, cash balance, and the portfolio summary from those results. Market prices are retrieved through a separate service when available; a manual price can be supplied when provider data is missing or unsuitable. Refreshing is supported by both a periodic background worker and a request-driven queue.\n\n## System design\n\nThe React and TypeScript client communicates with an ASP.NET Core API on the same origin. The API accesses PostgreSQL through EF Core and requests market data from a FastAPI-based market-data-service when needed. This keeps provider-specific concerns separate from the .NET API that owns financial calculations.\n\nThe core model has four parts:\n\n- **Transactions** are the immutable source record for portfolio activity.\n- **Assets** describe catalog-backed or user-created assets.\n- **MarketPrices** hold the latest provider or manual price.\n- **PortfolioPositions** are a rebuildable read model for fast interface queries.\n\n## Key technical decisions\n\nKeeping the transaction ledger as the source of truth and the positions table as a read model is a key decision. If a calculation rule or market price changes, positions can be rebuilt from transactions rather than treating presentation totals as history.\n\nCost is calculated with the weighted average cost method. Sales update the remaining cost basis, realised results are tracked separately, and small residual quantities are handled with a closing tolerance. Calculation rules are not repeated in the UI; the API remains their single owner.\n\nMarket data lives behind a separate FastAPI service, isolating dependencies such as borsapy from the financial core. Provider symbols are normalised and availability is carried to the API as part of the price contract.\n\n## Validation and limits\n\nThe API has integration tests using real PostgreSQL Testcontainers, the React layer uses Vitest and Testing Library, and the Python service uses pytest. This validates financial calculations, HTTP contracts, and the market-data bridge independently.\n\nThe project is a personal educational tool, not a multi-user investment platform or financial advisory system. Price accuracy and availability depend on external providers. A missing price does not mean the application can make an investment decision on the user's behalf.",
          "contentFormat": "markdown",
          "publishedAt": "2026-09-12T00:00:00.000Z",
          "updatedAt": "2026-09-12T00:00:00.000Z",
          "kind": "project",
          "description": "A personal portfolio application that keeps transactions as the source of truth and separates price data from portfolio calculations.",
          "repositoryUrl": "https://github.com/pitonmert/portfolio-tracker",
          "technologies": [
            "ASP.NET Core",
            "React",
            "TypeScript",
            "EF Core",
            "PostgreSQL",
            "FastAPI",
            "Testcontainers"
          ],
          "categories": [
            "Web Application",
            "Data Modelling"
          ],
          "createdAt": "2026-04-28T09:01:29.000Z",
          "featured": true,
          "featuredOrder": 0
        },
        {
          "id": "project:word-match",
          "locale": "en",
          "title": "word-match",
          "canonicalUrl": "https://www.pitonmert.com/en/projects/word-match",
          "content": "## Context and purpose\n\nword-match turns English vocabulary practice for Turkish speakers from a collection of random questions into a structured learning flow. Rather than asking users to choose from many modes, it offers one Study surface: the system selects the next appropriate topic, while the user can still change that topic deliberately.\n\nThe goal is not only to count correct answers. A persistent model tracks when each word should be revisited in each skill dimension. Sessions, answers, and mastery data are retained with the user account.\n\n## How it works\n\nThe curriculum is structured as level, ordered topic, active learning group, and ordered word. The Study home either resumes an active session or presents the next suitable topic. A topic session balances recognising meaning and recalling the same words in writing.\n\nAfter an answer, the API records the outcome and updates mastery for the relevant word. Users can temporarily defer writing questions that are not suitable at that moment; a deferral is not treated as an answer. Completed or interrupted sessions remain available with detailed results.\n\n## System design\n\nThe ASP.NET Core API organises authentication, Study planning, question generation, word catalogues, and persistence as feature-focused areas. The React client keeps session state and API interactions in separate features. PostgreSQL persists users, curriculum records, sessions, question snapshots, and mastery data.\n\nContent starts from a CSV source. Its stable ImportKey is separate from the database-generated identity, so the idempotent bootstrap command can update words and curriculum placement without unnecessarily resetting user progress.\n\n## Key technical decisions\n\nQuestions are planned when a Study session is created, and question and answer snapshots are retained. Later catalogue edits therefore cannot rewrite the history of an active session. For multiple-choice questions, distractors are drawn from the session first, then the same level, and finally a wider catalogue if needed.\n\nMastery is tracked across skill dimensions rather than as one generic score. Correct answers move the review time forward, while wrong or unknown outcomes schedule an earlier return for the relevant skill. The planner selects only ready words and keeps reinforcement sessions deliberately small.\n\nSession writes, answer records, and progress updates share a data-integrity boundary. Identity cookies and XSRF protection safeguard authentication and state-changing requests.\n\n## Validation and limits\n\nThe API includes tests for Study planning, question generation, bootstrap behaviour, and endpoints. The client includes tests for sessions, authentication, and the vocabulary interface. Docker and nginx configuration support running the application behind a production-like network boundary.\n\nThe current product focus is the curriculum-based Study flow. Richer content types, advanced pronunciation assessment, and social or points systems are outside this core. Vocabulary quality depends on the source CSV; the application provides a consistent practice system rather than replacing a language teacher.",
          "contentFormat": "markdown",
          "publishedAt": "2026-09-12T00:00:00.000Z",
          "updatedAt": "2026-09-12T00:00:00.000Z",
          "kind": "project",
          "description": "An English vocabulary learning application for Turkish speakers, built around curriculum order, word-level mastery, and persistent Study sessions.",
          "repositoryUrl": "https://github.com/pitonmert/word-match",
          "technologies": [
            "ASP.NET Core",
            "React",
            "TypeScript",
            "EF Core",
            "PostgreSQL",
            "ASP.NET Core Identity",
            "Docker"
          ],
          "categories": [
            "Web Application",
            "Learning Technology"
          ],
          "createdAt": "2026-03-17T10:55:39.000Z",
          "featured": true,
          "featuredOrder": 2
        },
        {
          "id": "project:smart-line",
          "locale": "en",
          "title": "smart-line",
          "canonicalUrl": "https://www.pitonmert.com/en/projects/smart-line",
          "content": "## Context and purpose\n\nsmart-line is an IIoT prototype for bringing manual product sorting and production-line monitoring into one operator surface. Its goal is not merely to display sensor values: product flow, environmental conditions, fire events, and conveyor commands are all treated as explicit system responsibilities.\n\nThe project establishes clear boundaries between firmware, vision-based data collection, messaging infrastructure, the application API, and the dashboard. That makes it possible to develop and test the web interface, physical devices, and safety behaviour independently.\n\n## How it works\n\nESP32 firmware produces environmental and fire data. A separate Python vision service prepares barcode and QR results, while an ESP32-CAM provides the video stream. Device and vision messages reach the API through a TLS-protected MQTT broker.\n\nAPI background services process those messages, persist records in PostgreSQL, and publish live updates to the dashboard through SignalR. Operators can observe current measurements, alerts, production activity, and conveyor state in one interface. Commands never travel directly from the browser to a device; they pass through API authorisation and safety rules first.\n\n## System design\n\nThe system has six focused parts:\n\n- **SmartLine.Firmware** reads ESP32 sensors, maintains the MQTT connection, and owns local emergency-latch behaviour.\n- **SmartLine.Cam** exposes an ESP32-CAM MJPEG stream.\n- **SmartLine.Vision** uses OpenCV and code readers to turn physical product information into MQTT messages.\n- **SmartLine.API** contains the Minimal API, EF Core, PostgreSQL access, MQTT consumers, and SignalR hub.\n- **SmartLine.Dashboard** provides role-based operation, live data, and alert interaction through React.\n- **Infrastructure** keeps service boundaries explicit through Mosquitto, certificates, Caddy, and Compose configuration.\n\nHuman identity and device identity are separate. Human users have Viewer, Operator, or Admin roles, while devices use a dedicated Device identity model for messaging.\n\n## Key technical decisions\n\nThe MQTT layer uses mutual TLS, credentials, and principal-based ACL rules instead of anonymous or plaintext connections. Being able to connect to the broker therefore does not automatically grant permission to act on every topic.\n\nNormal stop, emergency stop, and administrator reset are distinct conveyor transitions. Firmware persists a device epoch, fire generation, and reset generation in NVS. A device with missing or corrupt state remains on the safe side, and an administrator reset does not automatically start the conveyor.\n\nThe API uses a feature-oriented Minimal API approach rather than a heavy abstraction stack. MQTT command transitions are serialised through a gate under the assumption of one active API instance, keeping safety rules out of scattered endpoint code.\n\n## Validation and limits\n\nThe API includes integration tests using real PostgreSQL Testcontainers, while the dashboard has lint and build checks, firmware has PlatformIO builds, and Vision has Python validation. Separating these layers helps catch changes to communication contracts earlier.\n\nThis is not a certified safety-critical hardware system. A servo position considered safe does not physically remove motor power; a real production line requires a hardwired emergency-stop circuit and safety relay. Command coordination also assumes one API instance, so horizontal scaling would require a distributed safety model.",
          "contentFormat": "markdown",
          "publishedAt": "2026-09-12T00:00:00.000Z",
          "updatedAt": "2026-09-12T00:00:00.000Z",
          "kind": "project",
          "description": "An IIoT production-line prototype combining ESP32 devices, machine vision, and a real-time operator interface.",
          "repositoryUrl": "https://github.com/pitonmert/smart-line",
          "technologies": [
            "ASP.NET Core",
            "React",
            "PostgreSQL",
            "MQTT",
            "SignalR",
            "ESP32",
            "Python",
            "Docker"
          ],
          "categories": [
            "IIoT",
            "Embedded Systems"
          ],
          "createdAt": "2026-02-17T10:07:55.000Z",
          "featured": true,
          "featuredOrder": 1
        },
        {
          "id": "project:media-stow",
          "locale": "en",
          "title": "media-stow",
          "canonicalUrl": "https://www.pitonmert.com/en/projects/media-stow",
          "content": "## Context and purpose\n\nmedia-stow is a command-line tool for organising large local archives of photos, videos, and audio. Instead of trusting filenames or directory locations, it compares file content, making cases such as identical files with different names easier to handle reliably.\n\nThe tool treats archiving as more than copying. Validating the target structure, finding duplicates, separating common files between directories, and comparing earlier operations all belong to the same workspace.\n\n## How it works\n\nAn initial synchronisation places source files into a category- and extension-based target structure, then creates a JSON index describing the result. Later synchronisations compare the disk with that index to identify new, missing, or misplaced files.\n\nThe dedup command groups files with equal SHA-256 hashes. Verify checks the structure of the sorted archive and can compare it with a source directory while producing a report. Extract-common and compare commands analyse shared or changed content between directories and operation records.\n\n## System design\n\nThe application is a single .NET console program that chooses commands through CommandRegistry. Each command owns its workflow, while hashing, file access, terminal logging, and user confirmation remain shared services.\n\nModels represent the media index, file records, summaries, and verification reports. Category and filter configuration centralises the rules that decide where files belong, avoiding different commands interpreting the same media rules independently.\n\n## Key technical decisions\n\nHashService reads each file as a stream and calculates SHA-256. Equality therefore is not inferred from a name, timestamp, or size: it establishes that two files contain the same bytes. For large archives, the cost is balanced by limiting parallel work to processor capacity.\n\nIndexes and reports are first written to a temporary target and then moved to their final name. This reduces the chance of a partially written JSON file being treated as valid state by a later synchronisation. Commands that change files ask for explicit confirmation, and sync and dedup provide a preview mode.\n\n## Validation and limits\n\nThe tool provides operational visibility through accessibility checks, structural verification, and JSON reports. However, the repository has no separate automated test project, so it does not offer automated proof that every file-moving or deletion scenario is safe.\n\nEqual hashes do not mean files are visually similar. A re-encoded or edited photo is treated as different content. Hashing requires reading every file, so large archives have expected disk-access and time costs. Preview mode and backups should be used before operating on real media.",
          "contentFormat": "markdown",
          "publishedAt": "2026-09-12T00:00:00.000Z",
          "updatedAt": "2026-09-12T00:00:00.000Z",
          "kind": "project",
          "description": "A .NET CLI for comparing media archives by content hash, with workflows for synchronisation, verification, and duplicate-file management.",
          "repositoryUrl": "https://github.com/pitonmert/media-stow",
          "technologies": [
            "C#",
            ".NET",
            "SHA-256",
            "JSON"
          ],
          "categories": [
            "Developer Tool",
            "CLI"
          ],
          "createdAt": "2026-01-04T11:40:41.000Z",
          "featured": false
        },
        {
          "id": "project:git-sync",
          "locale": "en",
          "title": "git-sync",
          "canonicalUrl": "https://www.pitonmert.com/en/projects/git-sync",
          "content": "## Context and purpose\n\ngit-sync grew from the need to keep many GitHub repositories orderly inside one local directory. It combines finding missing repositories, safely advancing outdated branches, and gathering shared Git configuration into one workflow.\n\nThe tool does not try to force every repository to the same state. Local work, diverged history, and broken repositories are made visible; decisions that risk data loss or automatic merging are not made without user confirmation.\n\n## How it works\n\nThe workflow has four stages. It first audits repositories in the target directory and reports broken ones. It then clones repositories available through GitHub CLI but missing locally. The update stage advances eligible branches with fast-forward operations, and the final stage aggregates gitignore and gitattributes rules into shared output files.\n\nBefore work begins, the tool validates Git, GitHub CLI, authentication, and the target directory. A dry-run can show planned cleanup, cloning, updating, and merging without changing files. Results are summarised in the terminal and can be exported as JSON.\n\n## System design\n\nThe application is split into Core, Application, and Infrastructure layers. Core holds options, result models, and service contracts. Application contains the orchestrator, validators, and workflow steps. Infrastructure implements Git, GitHub CLI, the filesystem, process execution, and Spectre.Console interaction.\n\nThat separation avoids binding Git commands and file access directly to workflow rules. Tests mock processes, filesystem access, and user interaction, allowing decisions to be tested without a live GitHub account or working directory.\n\n## Key technical decisions\n\nThe active tracking branch is updated only when fast-forward is possible. Other branches are checked for ancestry before their references advance, and diverged histories are never merged automatically. The tool refuses to trade convenience for losing local work.\n\nCloning and updates can run concurrently, but bounded parallelism keeps network and disk use under control for large repository sets. Submodules are handled when their parent repository changes.\n\nBroken-repository cleanup and output-producing merge steps are visible through dry-run. Preview mode is therefore a safety boundary for a workflow that can otherwise be destructive, not merely a reporting option.\n\n## Validation and limits\n\nUnit tests cover validation, broken-repository detection, cloning, updating, dry-run behaviour, and configuration merging. Process results and timeout handling can also be controlled through the abstraction layer.\n\nThe tool is not a general merge-conflict resolver. Local changes, detached HEAD, permission issues, and diverged branches are not resolved automatically. It requires Git, GitHub CLI, and an authenticated GitHub session, and should not be run with elevated privileges.",
          "contentFormat": "markdown",
          "publishedAt": "2026-09-12T00:00:00.000Z",
          "updatedAt": "2026-09-12T00:00:00.000Z",
          "kind": "project",
          "description": "A .NET CLI that audits, safely updates, and consolidates Git configuration across multiple GitHub repositories.",
          "repositoryUrl": "https://github.com/pitonmert/git-sync",
          "technologies": [
            "C#",
            ".NET",
            "Git",
            "GitHub CLI",
            "Spectre.Console"
          ],
          "categories": [
            "Developer Tool",
            "CLI"
          ],
          "createdAt": "2026-01-04T11:38:56.000Z",
          "featured": false
        }
      ],
      "writing": [
        {
          "id": "writing:sql-quick-reference",
          "locale": "en",
          "title": "What is SQL?",
          "canonicalUrl": "https://www.pitonmert.com/en/writing/sql-quick-reference",
          "content": "# What is SQL?\n\nSQL is a language for querying and changing data and managing structures such as tables\nin relational databases. You can find customers by city, sum order amounts, or add records.\n\nExamples are independent and use PostgreSQL syntax. The schema and data describe\neach example's starting state; output shows the last data query's result or completion information.\n\n## Querying Data — DQL\n\nDQL (*Data Query Language*) groups expressions that read data. SELECT is a query;\nclauses such as WHERE are parts of it. This learning classification separates SELECT;\nsome references include it in DML.\n\n### SELECT\n\n*Query result / Column*\n\nChooses the columns to return.\n\n**Example schema and data**\n\n```text\ncustomers(id INTEGER, name TEXT)\n1 | Avery\n2 | Morgan\n```\n\n**Query**\n\n```sql\nSELECT name\nFROM customers\nORDER BY id;\n```\n\n**Output**\n\n```text\nname\nAvery\nMorgan\n```\n\n### AS (Alias)\n\n*Column / Table*\n\nGives a column or table a temporary name within a query.\n\n**Example schema and data**\n\n```text\ncustomers(id INTEGER, name TEXT)\n1 | Avery\n```\n\n**Query**\n\n```sql\nSELECT m.name AS customer_name\nFROM customers AS m;\n```\n\n**Output**\n\n```text\ncustomer_name\nAvery\n```\n\n### DISTINCT\n\n*Result rows*\n\nRemoves duplicate result rows formed by the selected fields.\n\n**Example schema and data**\n\n```text\ncustomers(id INTEGER, city TEXT)\n1 | Bristol\n2 | Bristol\n3 | London\n```\n\n**Query**\n\n```sql\nSELECT DISTINCT city\nFROM customers\nORDER BY city;\n```\n\n**Output**\n\n```text\ncity\nBristol\nLondon\n```\n\n### WHERE\n\n*Row*\n\nSelects rows matching a condition.\n\n**Example schema and data**\n\n```text\ncustomers(id INTEGER, name TEXT, city TEXT)\n1 | Avery   | London\n2 | Morgan | Bristol\n```\n\n**Query**\n\n```sql\nSELECT name\nFROM customers\nWHERE city = 'London';\n```\n\n**Output**\n\n```text\nname\nAvery\n```\n\n### ORDER BY\n\n*Result rows*\n\nSorts results in ascending (ASC) or descending (DESC) order.\n\n**Example schema and data**\n\n```text\norders(id INTEGER, amount INTEGER)\n1 | 300\n2 | 900\n```\n\n**Query**\n\n```sql\nSELECT id, amount\nFROM orders\nORDER BY amount DESC, id;\n```\n\n**Output**\n\n```text\nid | amount\n2  | 900\n1  | 300\n```\n\n### LIMIT / OFFSET\n\n*Result rows*\n\nLimits the result count and skips rows at the beginning.\n\n**Example schema and data**\n\n```text\norders(id INTEGER, amount INTEGER)\n1 | 300\n2 | 900\n3 | 600\n```\n\n**Query**\n\n```sql\nSELECT id, amount\nFROM orders\nORDER BY id\nLIMIT 2 OFFSET 1;\n```\n\n**Output**\n\n```text\nid | amount\n2  | 900\n3  | 600\n```\n\nThe first row is skipped. Use a unique ordering for stable pagination.\n\n## Relationships and Advanced Queries\n\nThese constructs combine tables, create intermediate results, and calculate values.\n\n### JOIN\n\n*Table / Result rows*\n\nCombines tables using a specified condition.\n\n**Example schema and data**\n\n```text\ncustomers(id INTEGER, name TEXT)\n1 | Avery\n2 | Morgan\n\norders(id INTEGER, customer_id INTEGER)\n10 | 1\n```\n\n**Query**\n\n```sql\nSELECT m.name, s.id AS order_number\nFROM customers AS m\nLEFT JOIN orders AS s ON s.customer_id = m.id\nORDER BY m.id;\n```\n\n**Output**\n\n```text\nname     | order_number\nAvery   | 10\nMorgan | NULL\n```\n\nLEFT JOIN also keeps unmatched left rows; INNER JOIN returns only matches.\n\n### SUBQUERY\n\n*Query / Intermediate result*\n\nUses one query's result inside another query.\n\n**Example schema and data**\n\n```text\norders(id INTEGER, amount INTEGER)\n1 | 300\n2 | 900\n```\n\n**Query**\n\n```sql\nSELECT id, amount\nFROM orders\nWHERE amount > (SELECT AVG(amount) FROM orders);\n```\n\n**Output**\n\n```text\nid | amount\n2  | 900\n```\n\nThe average is 600; only the order above that average is returned.\n\n### CTE (WITH)\n\n*Query / Named intermediate result*\n\nNames an intermediate result for use within a statement.\n\n**Example schema and data**\n\n```text\norders(id INTEGER, customer_id INTEGER, amount INTEGER)\n1 | 1 | 300\n2 | 1 | 900\n3 | 2 | 200\n```\n\n**Query**\n\n```sql\nWITH totals AS (\n  SELECT customer_id, SUM(amount) AS total\n  FROM orders\n  GROUP BY customer_id\n)\nSELECT customer_id, total\nFROM totals\nWHERE total > 1000;\n```\n\n**Output**\n\n```text\ncustomer_id | total\n1          | 1200\n```\n\n### CASE WHEN\n\n*Column / Value*\n\nProduces a result value based on a condition.\n\n**Example schema and data**\n\n```text\norders(id INTEGER, amount INTEGER)\n1 | 300\n2 | 900\n```\n\n**Query**\n\n```sql\nSELECT id,\n  CASE WHEN amount >= 500 THEN 'High'\n       ELSE 'Standard' END AS level\nFROM orders\nORDER BY id;\n```\n\n**Output**\n\n```text\nid | level\n1  | Standard\n2  | High\n```\n\n### UNION / UNION ALL\n\n*Result set*\n\nAppends compatible query results.\n\n**Example schema and data**\n\n```text\nactive_customers(id INTEGER): 1, 2\nformer_customers(id INTEGER): 2, 3\n```\n\n**Query**\n\n```sql\nSELECT id FROM active_customers\nUNION\nSELECT id FROM former_customers\nORDER BY id;\n```\n\n**Output**\n\n```text\nid\n1\n2\n3\n```\n\nUNION removes duplicates. Using UNION ALL in this query yields 1, 2, 2, 3.\n\n### INTERSECT / EXCEPT\n\n*Result set*\n\nFinds shared rows or rows present only in the first set.\n\n**Example schema and data**\n\n```text\nactive_customers(id INTEGER): 1, 2\nformer_customers(id INTEGER): 2, 3\n```\n\n**Query**\n\n```sql\nSELECT id FROM active_customers\nINTERSECT\nSELECT id FROM former_customers;\n```\n\n**Output**\n\n```text\nid\n2\n```\n\nReplacing INTERSECT with EXCEPT returns 1.\n\n### AGGREGATE (COUNT / SUM / AVG)\n\n*Row set / Summary value*\n\nProduces a summary from multiple rows.\n\n**Example schema and data**\n\n```text\norders(id INTEGER, amount INTEGER)\n1 | 300\n2 | 900\n```\n\n**Query**\n\n```sql\nSELECT COUNT(*) AS count,\n       SUM(amount) AS total,\n       AVG(amount) AS average\nFROM orders;\n```\n\n**Output**\n\n```text\ncount | total | average\n2    | 1200   | 600\n```\n\nDecimal formatting of the average may vary by client. MIN and MAX find the smallest and largest values.\n\n### GROUP BY\n\n*Row group*\n\nGroups rows sharing the same values.\n\n**Example schema and data**\n\n```text\norders(id INTEGER, customer_id INTEGER)\n1 | 1\n2 | 1\n3 | 2\n```\n\n**Query**\n\n```sql\nSELECT customer_id, COUNT(*) AS count\nFROM orders\nGROUP BY customer_id\nORDER BY customer_id;\n```\n\n**Output**\n\n```text\ncustomer_id | count\n1          | 2\n2          | 1\n```\n\n### HAVING\n\n*Row group*\n\nFilters groups after grouping.\n\n**Example schema and data**\n\n```text\norders(id INTEGER, customer_id INTEGER)\n1 | 1\n2 | 1\n3 | 2\n```\n\n**Query**\n\n```sql\nSELECT customer_id, COUNT(*) AS count\nFROM orders\nGROUP BY customer_id\nHAVING COUNT(*) >= 2;\n```\n\n**Output**\n\n```text\ncustomer_id | count\n1          | 2\n```\n\nWHERE selects rows before grouping; HAVING selects groups.\n\n### WINDOW FUNCTIONS (OVER / PARTITION BY)\n\n*Row / Calculated column*\n\nCalculates row numbers or running totals while retaining individual rows.\n\n**Example schema and data**\n\n```text\norders(id INTEGER, customer_id INTEGER, amount INTEGER)\n1 | 1 | 300\n2 | 1 | 900\n3 | 2 | 200\n```\n\n**Query**\n\n```sql\nSELECT id, customer_id,\n  SUM(amount) OVER (\n    PARTITION BY customer_id ORDER BY id\n    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n  ) AS running_total\nFROM orders\nORDER BY id;\n```\n\n**Output**\n\n```text\nid | customer_id | running_total\n1  | 1          | 300\n2  | 1          | 1200\n3  | 2          | 200\n```\n\n## Changing Data — DML\n\nDML (*Data Manipulation Language*) covers inserting, changing, and deleting records.\n\n### INSERT\n\n*Row*\n\nAdds a record to a table.\n\n**Example schema and data**\n\n```text\ncustomers(id INTEGER PRIMARY KEY, name TEXT)\nInitially empty.\n```\n\n**Query**\n\n```sql\nINSERT INTO customers (id, name)\nVALUES (1, 'Avery')\nRETURNING id, name;\n```\n\nRETURNING returns the specified fields from the inserted row.\n\n**Output**\n\n```text\nid | name\n1  | Avery\n```\n\n### UPDATE\n\n*Row*\n\nChanges selected fields in matching records.\n\n**Example schema and data**\n\n```text\ncustomers(id INTEGER PRIMARY KEY, name TEXT, city TEXT)\n1 | Avery | London\n```\n\n**Query**\n\n```sql\nUPDATE customers\nSET city = 'Bristol'\nWHERE id = 1\nRETURNING id, city;\n```\n\n**Output**\n\n```text\nid | city\n1  | Bristol\n```\n\nWithout WHERE, every row is updated.\n\n### DELETE\n\n*Row*\n\nDeletes matching rows while retaining the table structure.\n\n**Example schema and data**\n\n```text\norders(id INTEGER PRIMARY KEY, amount INTEGER)\n1 | 300\n2 | 900\n```\n\n**Query**\n\n```sql\nDELETE FROM orders\nWHERE id = 2\nRETURNING id, amount;\n```\n\n**Output**\n\n```text\nid | amount\n2  | 900\n```\n\nThe output is the deleted row; only order 1 remains. Without WHERE, every row is deleted.\n\n## Defining Structures — DDL\n\nDDL (*Data Definition Language*) manages tables and other database objects.\n\n### CREATE\n\n*Database object / Table*\n\nCreates a database object.\n\n**Example schema and data**\n\n```text\nThe categories table does not exist yet.\nTarget: categories(id INTEGER PRIMARY KEY, name TEXT NOT NULL)\n```\n\n**Query**\n\n```sql\nCREATE TABLE categories (\n  id INTEGER PRIMARY KEY,\n  name TEXT NOT NULL\n);\n```\n\n**Output**\n\n```text\nCREATE TABLE\n```\n\nAn empty table with the specified columns is created.\n\n### ALTER\n\n*Table structure*\n\nChanges an existing table's structure.\n\n**Example schema and data**\n\n```text\ncustomers(id INTEGER PRIMARY KEY, name TEXT)\n1 | Avery\n```\n\n**Query**\n\n```sql\nALTER TABLE customers ADD COLUMN phone TEXT;\nSELECT id, name, phone FROM customers;\n```\n\n**Output**\n\n```text\nid | name   | phone\n1  | Avery | NULL\n```\n\n### DROP\n\n*Database object*\n\nRemoves an object and its data.\n\n**Example schema and data**\n\n```text\ncategories(id INTEGER PRIMARY KEY, name TEXT)\n1 | Book\nNo other objects depend on the table.\n```\n\n**Query**\n\n```sql\nDROP TABLE categories;\n```\n\n**Output**\n\n```text\nDROP TABLE\n```\n\nThe table no longer exists; querying it again raises an error.\n\n### TRUNCATE\n\n*Table / All rows*\n\nRemoves all rows while retaining the table structure.\n\n**Example schema and data**\n\n```text\norders(id INTEGER PRIMARY KEY, amount INTEGER)\n1 | 300\n2 | 900\nNo foreign keys reference the table.\n```\n\n**Query**\n\n```sql\nTRUNCATE TABLE orders;\nSELECT COUNT(*) AS remaining_rows FROM orders;\n```\n\n**Output**\n\n```text\nremaining_rows\n0\n```\n\nIt does not accept WHERE. In PostgreSQL, it can be rolled back within an open transaction.\n\n### PRIMARY KEY / FOREIGN KEY\n\n*Column / Table integrity*\n\nA primary key provides unique identity; a foreign key references an existing record.\n\n**Example schema and data**\n\n```text\ncustomers(id INTEGER PRIMARY KEY)\n1\n\nTarget: addresses(id INTEGER PRIMARY KEY,\n               customer_id INTEGER NOT NULL REFERENCES customers(id))\n```\n\n**Query**\n\n```sql\nCREATE TABLE addresses (\n  id INTEGER PRIMARY KEY,\n  customer_id INTEGER NOT NULL REFERENCES customers(id)\n);\nINSERT INTO addresses (id, customer_id)\nVALUES (10, 1)\nRETURNING id, customer_id;\n```\n\n**Output**\n\n```text\nid | customer_id\n10 | 1\n```\n\nA missing customer ID causes a foreign key error. A primary key rejects duplicates and NULL.\n\n### CHECK / DEFAULT / NOT NULL\n\n*Column / Data rules*\n\nCHECK validates a condition, DEFAULT supplies a value, and NOT NULL rejects missing values.\n\n**Example schema and data**\n\n```text\nThe products table does not exist yet.\nTarget: products(id INTEGER PRIMARY KEY, price INTEGER NOT NULL,\n              stock INTEGER NOT NULL DEFAULT 0)\nRule: price and stock cannot be negative.\n```\n\n**Query**\n\n```sql\nCREATE TABLE products (\n  id INTEGER PRIMARY KEY,\n  price INTEGER NOT NULL CHECK (price >= 0),\n  stock INTEGER NOT NULL DEFAULT 0 CHECK (stock >= 0)\n);\nINSERT INTO products (id, price)\nVALUES (1, 100)\nRETURNING id, price, stock;\n```\n\n**Output**\n\n```text\nid | price | stock\n1  | 100   | 0\n```\n\nDEFAULT does not replace an explicitly supplied NULL.\n\n### VIEW\n\n*Virtual table*\n\nMakes a query reusable through a virtual table name.\n\n**Example schema and data**\n\n```text\norders(id INTEGER, status TEXT)\n1 | shipped\n2 | preparing\nThe view does not exist yet.\n```\n\n**Query**\n\n```sql\nCREATE VIEW shipped_orders AS\nSELECT id FROM orders WHERE status = 'shipped';\nSELECT id FROM shipped_orders;\n```\n\n**Output**\n\n```text\nid\n1\n```\n\nA regular view does not store a separate copy of its results.\n\n### INDEX\n\n*Table / Access path*\n\nCreates an index that may speed up selected searches and joins.\n\n**Example schema and data**\n\n```text\norders(id INTEGER, customer_id INTEGER)\n1 | 10\n2 | 20\nThe index does not exist yet.\n```\n\n**Query**\n\n```sql\nCREATE INDEX idx_orders_customer\nON orders (customer_id);\n```\n\n**Output**\n\n```text\nCREATE INDEX\n```\n\nRows do not change. The index adds storage and write costs; the query planner decides whether to use it.\n\n### TRIGGER\n\n*Table / Event*\n\nAutomatically runs an operation on a data event.\n\n**Example schema and data**\n\n```text\ncustomers(id INTEGER PRIMARY KEY, name TEXT)\nInitially empty; the function and trigger below do not exist yet.\n```\n\n**Query**\n\n```sql\nCREATE FUNCTION trim_name() RETURNS trigger\nLANGUAGE plpgsql AS $$\nBEGIN\n  NEW.name := trim(NEW.name);\n  RETURN NEW;\nEND;\n$$;\nCREATE TRIGGER customer_name_cleanup\nBEFORE INSERT ON customers\nFOR EACH ROW EXECUTE FUNCTION trim_name();\n\nINSERT INTO customers (id, name)\nVALUES (1, '  Avery  ')\nRETURNING id, name;\n```\n\n**Output**\n\n```text\nid | name\n1  | Avery\n```\n\nThis is a PostgreSQL example. Leading and trailing spaces are removed before insertion.\n\n## Managing Transactions — TCL\n\nTCL (*Transaction Control Language*) manages changes together within a transaction.\n\n### BEGIN\n\n*Transaction / Data changes*\n\nStarts a transaction to manage changes together in the same session.\n\n**Example schema and data**\n\n```text\norders(id INTEGER PRIMARY KEY, status TEXT)\n1 | preparing\n```\n\n**Query**\n\n```sql\nBEGIN;\nUPDATE orders SET status = 'shipped' WHERE id = 1;\nSELECT status FROM orders WHERE id = 1;\nROLLBACK;\n```\n\n**Output**\n\n```text\nstatus\nshipped\n```\n\nSELECT shows the change inside the transaction. The final ROLLBACK restores preparing.\n\n### COMMIT\n\n*Transaction / Persistence*\n\nMakes changes in the transaction persistent.\n\n**Example schema and data**\n\n```text\ncustomers(id INTEGER PRIMARY KEY, city TEXT)\n1 | London\n```\n\n**Query**\n\n```sql\nBEGIN;\nUPDATE customers SET city = 'Bristol' WHERE id = 1;\nCOMMIT;\nSELECT city FROM customers WHERE id = 1;\n```\n\n**Output**\n\n```text\ncity\nBristol\n```\n\nAfter commit, ROLLBACK does not undo this change.\n\n### ROLLBACK\n\n*Transaction / Undo*\n\nUndoes uncommitted changes in the open transaction.\n\n**Example schema and data**\n\n```text\norders(id INTEGER PRIMARY KEY, amount INTEGER)\n1 | 300\n```\n\n**Query**\n\n```sql\nBEGIN;\nDELETE FROM orders WHERE id = 1;\nROLLBACK;\nSELECT id, amount FROM orders;\n```\n\n**Output**\n\n```text\nid | amount\n1  | 300\n```\n\n## Managing Permissions — DCL\n\nDCL (*Data Control Language*) manages user and role access to objects.\n\n### GRANT\n\n*Role / Database object*\n\nGrants a role a privilege on an object.\n\n**Example schema and data**\n\n```text\norders(id INTEGER, amount INTEGER)\nExisting role: reporting_role\nThe session owns the table; the role initially has no read privilege.\n```\n\n**Query**\n\n```sql\nGRANT SELECT ON orders TO reporting_role;\n```\n\n**Output**\n\n```text\nGRANT\n```\n\nThe role receives table read permission; connection and schema access may also be needed.\n\n### REVOKE\n\n*Role / Database object*\n\nRemoves a previously granted privilege.\n\n**Example schema and data**\n\n```text\norders(id INTEGER, amount INTEGER)\nExisting role: reporting_role\nThe session owns the table; SELECT was granted directly to the role.\n```\n\n**Query**\n\n```sql\nREVOKE SELECT ON orders FROM reporting_role;\n```\n\n**Output**\n\n```text\nREVOKE\n```\n\nThe direct grant is removed; access through other role memberships or PUBLIC may remain.\n\n## Quick notes\n\n- Use `IS NULL` to check for NULL; NULL, an empty string, and zero are different.\n- `COUNT(*)` counts rows; `COUNT(column)` counts non-NULL values.\n- Result order is not guaranteed without `ORDER BY`.\n- Use the driver's parameter binding instead of appending user input to SQL text.\n\n## References\n\n- [PostgreSQL: SQL commands](https://www.postgresql.org/docs/current/sql-commands.html)\n- [PostgreSQL: transactions](https://www.postgresql.org/docs/current/tutorial-transactions.html)",
          "contentFormat": "markdown",
          "publishedAt": "2026-09-13T00:00:00.000Z",
          "updatedAt": "2026-09-14T00:00:00.000Z",
          "kind": "writing",
          "description": "The language used to query and change data and manage table structures in relational databases.",
          "tags": [
            "SQL",
            "PostgreSQL",
            "Reference"
          ]
        }
      ]
    }
  }
}
