feat(blog): isolate articles by locale and add initial english posts
This commit is contained in:
@@ -7,14 +7,14 @@ import { getBlogDetailBySlug } from "@/data/blog-detail";
|
|||||||
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://poyrazavsever.com";
|
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://poyrazavsever.com";
|
||||||
|
|
||||||
type BlogDetailPageProps = {
|
type BlogDetailPageProps = {
|
||||||
params: Promise<{ slug: string }>;
|
params: Promise<{ locale: string; slug: string }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function generateMetadata({ params }: BlogDetailPageProps): Promise<Metadata> {
|
export async function generateMetadata({ params }: BlogDetailPageProps): Promise<Metadata> {
|
||||||
const { slug } = await params;
|
const { locale, slug } = await params;
|
||||||
const post = await getBlogDetailBySlug(slug);
|
const post = await getBlogDetailBySlug(slug);
|
||||||
|
|
||||||
if (!post) {
|
if (!post || post.lang !== locale) {
|
||||||
return {
|
return {
|
||||||
title: "Yazı Bulunamadı",
|
title: "Yazı Bulunamadı",
|
||||||
};
|
};
|
||||||
@@ -51,10 +51,10 @@ export async function generateMetadata({ params }: BlogDetailPageProps): Promise
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function BlogDetailPage({ params }: BlogDetailPageProps) {
|
export default async function BlogDetailPage({ params }: BlogDetailPageProps) {
|
||||||
const { slug } = await params;
|
const { locale, slug } = await params;
|
||||||
const post = await getBlogDetailBySlug(slug);
|
const post = await getBlogDetailBySlug(slug);
|
||||||
|
|
||||||
if (!post) {
|
if (!post || post.lang !== locale) {
|
||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ import { BlogContent } from "@/components/blog-content";
|
|||||||
import { getBlogPageData } from "@/data/blog";
|
import { getBlogPageData } from "@/data/blog";
|
||||||
|
|
||||||
type BlogPageProps = {
|
type BlogPageProps = {
|
||||||
|
params: Promise<{ locale: string }>;
|
||||||
searchParams?: Promise<{ page?: string | string[]; category?: string | string[]; search?: string | string[] }>;
|
searchParams?: Promise<{ page?: string | string[]; category?: string | string[]; search?: string | string[] }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function BlogPage({ searchParams }: BlogPageProps) {
|
export default async function BlogPage({ params, searchParams }: BlogPageProps) {
|
||||||
|
const { locale } = await params;
|
||||||
const resolved = searchParams ? await searchParams : undefined;
|
const resolved = searchParams ? await searchParams : undefined;
|
||||||
const pageParam = Array.isArray(resolved?.page) ? resolved?.page[0] : resolved?.page;
|
const pageParam = Array.isArray(resolved?.page) ? resolved?.page[0] : resolved?.page;
|
||||||
const categoryParam = Array.isArray(resolved?.category)
|
const categoryParam = Array.isArray(resolved?.category)
|
||||||
@@ -14,7 +16,7 @@ export default async function BlogPage({ searchParams }: BlogPageProps) {
|
|||||||
const searchParam = Array.isArray(resolved?.search) ? resolved?.search[0] : resolved?.search;
|
const searchParam = Array.isArray(resolved?.search) ? resolved?.search[0] : resolved?.search;
|
||||||
const page = Number(pageParam ?? "1");
|
const page = Number(pageParam ?? "1");
|
||||||
const currentPage = Number.isFinite(page) && page > 0 ? Math.floor(page) : 1;
|
const currentPage = Number.isFinite(page) && page > 0 ? Math.floor(page) : 1;
|
||||||
const data = await getBlogPageData(currentPage, 12, categoryParam, searchParam);
|
const data = await getBlogPageData(locale, currentPage, 12, categoryParam, searchParam);
|
||||||
|
|
||||||
return <BlogContent data={data} />;
|
return <BlogContent data={data} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,13 @@ import { ReferencesSection } from "@/components/references-section";
|
|||||||
import { SponsorsSection } from "@/components/sponsors-section";
|
import { SponsorsSection } from "@/components/sponsors-section";
|
||||||
import { getHomeBlogNews } from "@/data/blog";
|
import { getHomeBlogNews } from "@/data/blog";
|
||||||
|
|
||||||
export default async function Home() {
|
export default async function Home({
|
||||||
const homeNews = await getHomeBlogNews(3);
|
params,
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: string }>;
|
||||||
|
}) {
|
||||||
|
const { locale } = await params;
|
||||||
|
const homeNews = await getHomeBlogNews(locale, 3);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="flex h-full flex-col gap-4 overflow-y-auto overflow-x-hidden">
|
<section className="flex h-full flex-col gap-4 overflow-y-auto overflow-x-hidden">
|
||||||
|
|||||||
+11
-10
@@ -1,9 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useCallback } from "react";
|
import { useState, useCallback } from "react";
|
||||||
import Link from "next/link";
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import { Badge, Card, Typography } from "poyraz-ui/atoms";
|
import { Badge, Card, Typography } from "poyraz-ui/atoms";
|
||||||
import {
|
import {
|
||||||
ArticleCard,
|
ArticleCard,
|
||||||
@@ -34,6 +34,7 @@ function buildHref(params: { page?: number; category?: string; search?: string }
|
|||||||
|
|
||||||
export function BlogContent({ data }: BlogContentProps) {
|
export function BlogContent({ data }: BlogContentProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const t = useTranslations("Blog");
|
||||||
const [searchInput, setSearchInput] = useState(data.searchQuery);
|
const [searchInput, setSearchInput] = useState(data.searchQuery);
|
||||||
const pageNumbers = Array.from({ length: data.totalPages }, (_, i) => i + 1);
|
const pageNumbers = Array.from({ length: data.totalPages }, (_, i) => i + 1);
|
||||||
const hasArticles = data.articles.length > 0;
|
const hasArticles = data.articles.length > 0;
|
||||||
@@ -59,7 +60,7 @@ export function BlogContent({ data }: BlogContentProps) {
|
|||||||
{/* Kategoriler */}
|
{/* Kategoriler */}
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<Typography variant="small" className="mr-1 text-muted-foreground">
|
<Typography variant="small" className="mr-1 text-muted-foreground">
|
||||||
Kategori:
|
{t("categories")}:
|
||||||
</Typography>
|
</Typography>
|
||||||
{data.categories.map((category) => (
|
{data.categories.map((category) => (
|
||||||
<Link
|
<Link
|
||||||
@@ -92,7 +93,7 @@ export function BlogContent({ data }: BlogContentProps) {
|
|||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Enter") submitSearch(searchInput);
|
if (e.key === "Enter") submitSearch(searchInput);
|
||||||
}}
|
}}
|
||||||
placeholder="Başlık veya içerikte ara..."
|
placeholder={t("searchPlaceholder")}
|
||||||
className="w-full rounded-sm border border-border bg-background py-2 pr-10 pl-9 text-sm text-foreground placeholder:text-muted-foreground focus:border-red-600 focus:ring-1 focus:ring-red-600/30 focus:outline-none"
|
className="w-full rounded-sm border border-border bg-background py-2 pr-10 pl-9 text-sm text-foreground placeholder:text-muted-foreground focus:border-red-600 focus:ring-1 focus:ring-red-600/30 focus:outline-none"
|
||||||
/>
|
/>
|
||||||
{searchInput && (
|
{searchInput && (
|
||||||
@@ -115,7 +116,7 @@ export function BlogContent({ data }: BlogContentProps) {
|
|||||||
{(data.searchQuery || data.selectedCategory !== "All") && (
|
{(data.searchQuery || data.selectedCategory !== "All") && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Typography variant="small" className="text-muted-foreground">
|
<Typography variant="small" className="text-muted-foreground">
|
||||||
{data.articles.length} sonuç
|
{data.articles.length} {t("results")}
|
||||||
{data.searchQuery ? ` · "${data.searchQuery}"` : ""}
|
{data.searchQuery ? ` · "${data.searchQuery}"` : ""}
|
||||||
{data.selectedCategory !== "All" ? ` · ${data.selectedCategory}` : ""}
|
{data.selectedCategory !== "All" ? ` · ${data.selectedCategory}` : ""}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -125,7 +126,7 @@ export function BlogContent({ data }: BlogContentProps) {
|
|||||||
className="inline-flex cursor-pointer items-center gap-1 rounded-sm border border-border px-2 py-0.5 text-xs text-muted-foreground transition-colors hover:text-foreground"
|
className="inline-flex cursor-pointer items-center gap-1 rounded-sm border border-border px-2 py-0.5 text-xs text-muted-foreground transition-colors hover:text-foreground"
|
||||||
>
|
>
|
||||||
<Icon icon="mdi:filter-remove-outline" width={14} height={14} />
|
<Icon icon="mdi:filter-remove-outline" width={14} height={14} />
|
||||||
Temizle
|
{t("clear")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -160,10 +161,10 @@ export function BlogContent({ data }: BlogContentProps) {
|
|||||||
/>
|
/>
|
||||||
<Typography variant="p" className="text-muted-foreground">
|
<Typography variant="p" className="text-muted-foreground">
|
||||||
{data.searchQuery
|
{data.searchQuery
|
||||||
? `"${data.searchQuery}" aramasına uygun sonuç bulunamadı.`
|
? t("noSearch", { search: data.searchQuery })
|
||||||
: data.selectedCategory === "All"
|
: data.selectedCategory === "All"
|
||||||
? "Henüz blog yazısı bulunmuyor."
|
? t("empty")
|
||||||
: `"${data.selectedCategory}" kategorisinde henüz blog yazısı bulunmuyor.`}
|
: t("emptyCategory", { category: data.selectedCategory })}
|
||||||
</Typography>
|
</Typography>
|
||||||
{(data.searchQuery || data.selectedCategory !== "All") && (
|
{(data.searchQuery || data.selectedCategory !== "All") && (
|
||||||
<button
|
<button
|
||||||
@@ -172,7 +173,7 @@ export function BlogContent({ data }: BlogContentProps) {
|
|||||||
className="mt-3 inline-flex cursor-pointer items-center gap-1.5 rounded-sm border border-border px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:text-foreground"
|
className="mt-3 inline-flex cursor-pointer items-center gap-1.5 rounded-sm border border-border px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:text-foreground"
|
||||||
>
|
>
|
||||||
<Icon icon="mdi:filter-remove-outline" width={16} height={16} />
|
<Icon icon="mdi:filter-remove-outline" width={16} height={16} />
|
||||||
Filtreleri Temizle
|
{t("clearFilters")}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import { Link } from "@/i18n/routing";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||||
@@ -94,6 +95,7 @@ function extractText(children: React.ReactNode): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function MermaidBlock({ chart }: { chart: string }) {
|
function MermaidBlock({ chart }: { chart: string }) {
|
||||||
|
const t = useTranslations("Blog");
|
||||||
const [svg, setSvg] = useState("");
|
const [svg, setSvg] = useState("");
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const idRef = useRef(`mermaid-${Math.random().toString(36).slice(2)}`);
|
const idRef = useRef(`mermaid-${Math.random().toString(36).slice(2)}`);
|
||||||
@@ -117,7 +119,7 @@ function MermaidBlock({ chart }: { chart: string }) {
|
|||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setError("Mermaid diyagramı oluşturulamadı.");
|
setError(t("diagramError"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -126,7 +128,7 @@ function MermaidBlock({ chart }: { chart: string }) {
|
|||||||
return () => {
|
return () => {
|
||||||
mounted = false;
|
mounted = false;
|
||||||
};
|
};
|
||||||
}, [chart]);
|
}, [chart, t]);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
@@ -142,7 +144,7 @@ function MermaidBlock({ chart }: { chart: string }) {
|
|||||||
return (
|
return (
|
||||||
<Card className="rounded-sm border-border p-3">
|
<Card className="rounded-sm border-border p-3">
|
||||||
<Typography variant="small" className="text-muted-foreground">
|
<Typography variant="small" className="text-muted-foreground">
|
||||||
Diyagram hazırlanıyor...
|
{t("diagramLoading")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
@@ -162,6 +164,7 @@ const GISCUS_CATEGORY =
|
|||||||
const GISCUS_CATEGORY_ID = process.env.NEXT_PUBLIC_GISCUS_CATEGORY_ID || "";
|
const GISCUS_CATEGORY_ID = process.env.NEXT_PUBLIC_GISCUS_CATEGORY_ID || "";
|
||||||
|
|
||||||
export function BlogDetailContent({ post }: BlogDetailContentProps) {
|
export function BlogDetailContent({ post }: BlogDetailContentProps) {
|
||||||
|
const t = useTranslations("Blog");
|
||||||
const progressBarRef = useRef<HTMLDivElement | null>(null);
|
const progressBarRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [tocOpen, setTocOpen] = useState(false);
|
const [tocOpen, setTocOpen] = useState(false);
|
||||||
|
|
||||||
@@ -199,7 +202,7 @@ export function BlogDetailContent({ post }: BlogDetailContentProps) {
|
|||||||
href="/blog"
|
href="/blog"
|
||||||
className="inline-flex items-center rounded-sm border border-border px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:text-foreground"
|
className="inline-flex items-center rounded-sm border border-border px-3 py-1.5 text-sm text-muted-foreground transition-colors hover:text-foreground"
|
||||||
>
|
>
|
||||||
{"<- Blog'a dön"}
|
{t("backToBlog")}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<header className="space-y-3">
|
<header className="space-y-3">
|
||||||
@@ -270,7 +273,7 @@ export function BlogDetailContent({ post }: BlogDetailContentProps) {
|
|||||||
},
|
},
|
||||||
p: ({ node, children, ...props }) => {
|
p: ({ node, children, ...props }) => {
|
||||||
const hasImg = node?.children?.some(
|
const hasImg = node?.children?.some(
|
||||||
(c: any) => c.tagName === "img",
|
(c: unknown) => (c as { tagName?: string }).tagName === "img",
|
||||||
);
|
);
|
||||||
if (hasImg) {
|
if (hasImg) {
|
||||||
return (
|
return (
|
||||||
@@ -405,7 +408,7 @@ export function BlogDetailContent({ post }: BlogDetailContentProps) {
|
|||||||
{showGiscus && (
|
{showGiscus && (
|
||||||
<section className="border-t border-border pt-6">
|
<section className="border-t border-border pt-6">
|
||||||
<Typography variant="h3" className="mb-4">
|
<Typography variant="h3" className="mb-4">
|
||||||
Yorumlar
|
{t("comments")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<GiscusComments
|
<GiscusComments
|
||||||
repo={GISCUS_REPO}
|
repo={GISCUS_REPO}
|
||||||
@@ -430,7 +433,7 @@ export function BlogDetailContent({ post }: BlogDetailContentProps) {
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => setTocOpen(true)}
|
onClick={() => setTocOpen(true)}
|
||||||
className="fixed right-4 bottom-6 z-40 flex h-11 w-11 cursor-pointer items-center justify-center rounded-full border border-border bg-background shadow-lg transition-transform hover:scale-105 active:scale-95 lg:hidden"
|
className="fixed right-4 bottom-6 z-40 flex h-11 w-11 cursor-pointer items-center justify-center rounded-full border border-border bg-background shadow-lg transition-transform hover:scale-105 active:scale-95 lg:hidden"
|
||||||
aria-label="İçindekiler"
|
aria-label={t("toc")}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
icon="mdi:table-of-contents"
|
icon="mdi:table-of-contents"
|
||||||
@@ -449,7 +452,7 @@ export function BlogDetailContent({ post }: BlogDetailContentProps) {
|
|||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<div className="mb-3 flex items-center justify-between">
|
<div className="mb-3 flex items-center justify-between">
|
||||||
<Typography variant="large">İçindekiler</Typography>
|
<Typography variant="large">{t("toc")}</Typography>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={closeToc}
|
onClick={closeToc}
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
title: "For Order and Efficiency in Software Projects: What is Conventional Commits?"
|
||||||
|
category: "General"
|
||||||
|
date: "2024-08-23"
|
||||||
|
readTime: "3 min read"
|
||||||
|
author: "Poyraz Avsever"
|
||||||
|
slug: "conventional-commits"
|
||||||
|
excerpt: "For Order and Efficiency in Software Projects: What is Conventional Commits? In the software development process, we deal with many details beyond writing code. Commit messages are a critical part..."
|
||||||
|
coverImage: "/blog/images/yaz-l-m-projelerinde-d-zen-ve-verimlilik-i-in-conventional-commits-nedir-cover.jpg"
|
||||||
|
canonicalUrl: "https://medium.com/@poyrazavsever/yaz%C4%B1l%C4%B1m-projelerinde-d%C3%BCzen-ve-verimlilik-i%CC%87%C3%A7in-conventional-commits-nedir-4413e05ffbbf"
|
||||||
|
lang: "en"
|
||||||
|
---
|
||||||
|
|
||||||
|
# For Order and Efficiency in Software Projects: What is Conventional Commits?
|
||||||
|
|
||||||
|
In the software development process, we deal with many details beyond writing code. Commit messages are, of course, a critical part of this process. However, commit messages can sometimes be messy, incomprehensible, and unorganized. This is exactly where "**Conventional Commits**" comes into play.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## What is Conventional Commits?
|
||||||
|
|
||||||
|
Conventional Commits is a software standard that puts your commit messages into a specific format. The goal is to express clearly what each commit does and make the project history more understandable. Writing your commit messages according to this standard makes the project more organized, trackable, and sustainable. Let's inspect it together.
|
||||||
|
|
||||||
|
## Why Should We Use Conventional Commits?
|
||||||
|
|
||||||
|
### 1. Readability
|
||||||
|
|
||||||
|
The readability of the commit history is important for all of us. This standard ensures that changes made in the project can be easily tracked. In large projects, since it becomes difficult from time to time to understand which commit solved which issue or added which new feature, using such standards makes the work of developers easier.
|
||||||
|
|
||||||
|
### 2. Traceability and Transparency
|
||||||
|
|
||||||
|
Making our commit messages consistent and clear makes it easy to track changes in our project history. Especially in changes that break **backward compatibility**, these arrangements provide a major advantage.
|
||||||
|
|
||||||
|
## How Are These Commit Messages Written?
|
||||||
|
|
||||||
|
According to Conventional Commits, each commit message consists of three main parts:
|
||||||
|
|
||||||
|
1. **Summary (Title):** Indicates the type of the message and briefly what it did.
|
||||||
|
2. **Body:** Explains the details of the change. Tells why it was done and how it was done.
|
||||||
|
3. **Footer:** Changes that break compatibility like breaking changes or closed issues are specified here.
|
||||||
|
|
||||||
|
## Commit Types
|
||||||
|
|
||||||
|
Commit messages start with specific types. Here are the most commonly used types: [Click to inspect more detailed commit types.](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||||
|
|
||||||
|
* **feat:** Adding a new feature.
|
||||||
|
* **fix:** Fixing a bug.
|
||||||
|
* **docs:** Changes related only to documentation.
|
||||||
|
* **style:** Formatting that does not affect the meaning of the code (white-space, formatting, missing semi-colons, etc.).
|
||||||
|
* **refactor:** A code change that neither fixes a bug nor adds a feature.
|
||||||
|
|
||||||
|
**Let's examine an example Commit Message together:**
|
||||||
|
|
||||||
|
> feat(login): add JWT authentication
|
||||||
|
>
|
||||||
|
> Added JWT authentication to the login process to enhance security.
|
||||||
|
> This change involves updating the login controller and modifying the user model.
|
||||||
|
>
|
||||||
|
> BREAKING CHANGE: The user model now requires a JWT token for all login operations.
|
||||||
|
|
||||||
|
1. **Summary (Title):**
|
||||||
|
|
||||||
|
* `**feat:**`
|
||||||
|
Indicates the commit type. Here `feat` (feature) type is used, which shows that the commit adds a new feature to the project. Other types can also be used, e.g. `fix` (fixing a bug), `docs` (documentation updates), etc.
|
||||||
|
* `**(login):**`
|
||||||
|
The part specified in parentheses shows which module or section this feature or change affects. Here `login` is used, so the change made is related to the login process.
|
||||||
|
* `**add JWT authentication:**`
|
||||||
|
This explains the specific change made by the commit in a short and concise way. Here, it is stated that authentication with JWT (JSON Web Token) is added to the login process.
|
||||||
|
|
||||||
|
2. **Body:**
|
||||||
|
|
||||||
|
* **First Sentence:**
|
||||||
|
“Added JWT authentication to the login process to enhance security.”
|
||||||
|
This sentence explains the purpose and result of the change made. Here, it is stated that JWT authentication is added to the login process and this was done to enhance security.
|
||||||
|
* **Second Sentence:**
|
||||||
|
“This change involves updating the login controller and modifying the user model.”
|
||||||
|
This sentence explains in more detail which files or modules the change affected. Here, it is stated that the login controller is updated and the user model is modified.
|
||||||
|
|
||||||
|
3. **Footer:**
|
||||||
|
|
||||||
|
* `**BREAKING CHANGE:**`
|
||||||
|
This expression shows that there is a change that breaks compatibility. If a commit makes a change that will break the operation of the existing code, this must be specified. This ensures that other developers are aware of this change.
|
||||||
|
* **Detail:**
|
||||||
|
“The user model now requires a JWT token for all login operations.”
|
||||||
|
This explanation details what the breaking change is. Here, it is stated that the user model now requires a JWT token for all login operations. This indicates that other developers should be careful when applying this change.
|
||||||
|
|
||||||
|
## In Conclusion
|
||||||
|
|
||||||
|
Conventional Commits makes our software development process more organized, understandable, and efficient. By putting our commit messages into a specific structure, we make our project management more sustainable and traceable.
|
||||||
|
|
||||||
|
If you also want a more organized commit history in your projects, I highly recommend trying Conventional Commits.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
* [https://www.conventionalcommits.org/en/v1.0.0/](https://www.conventionalcommits.org/en/v1.0.0/)
|
||||||
|
* [https://developer.vonage.com/en/blog/3-reasons-why-you-should-use-conventional-commits](https://developer.vonage.com/en/blog/3-reasons-why-you-should-use-conventional-commits)
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
---
|
||||||
|
title: "What is Docker? — Grab a Coffee, Let's Talk Docker."
|
||||||
|
category: "General"
|
||||||
|
date: "2025-07-03"
|
||||||
|
readTime: "4 min read"
|
||||||
|
author: "Poyraz Avsever"
|
||||||
|
slug: "what-is-docker"
|
||||||
|
excerpt: "What is Docker? — Grab a Coffee, Let's Talk Docker. Let's have a chat: in the software development world, things can get a bit messy. An application that works in one place might not work in another..."
|
||||||
|
coverImage: "/blog/images/docker-nedir-kahveni-al-docker-konu-uyoruz-cover.jpg"
|
||||||
|
canonicalUrl: "https://medium.com/@poyrazavsever/docker-nedir-kahveni-al-docker%C4%B1-konu%C5%9Fuyoruz-de83718255e8"
|
||||||
|
lang: "en"
|
||||||
|
---
|
||||||
|
|
||||||
|
# What is Docker? — Grab a Coffee, Let's Talk Docker.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Let's have a chat: in the software development world, things can get a bit... messy. An application that works in one place doesn't work in another, and the phrase "it works on my machine" turns into an office legend. Of course, there are many reasons why an application might work for you but not for someone else. But we won't talk about those today. Today, we're talking about Docker, which promises to solve most of these problems. Now "Docker" enters our stage, and with the aura of a hero.
|
||||||
|
|
||||||
|
Docker is actually an open-source platform that allows us to put our applications and their dependencies, along with the environment they run in, into lightweight virtual boxes called "containers." But don't worry, in this article, we won't just chase technical definitions. I will explain it to you the same way I understood it myself. We will discover together what Docker is, why it is loved so much, and why developers can't put it down.
|
||||||
|
|
||||||
|
## What's in the Rest of the Article?
|
||||||
|
|
||||||
|
* Why did we need Docker?
|
||||||
|
* What exactly does Docker do?
|
||||||
|
* What is a container, and how is it different from a virtual machine?
|
||||||
|
* How to run the first container with Docker?
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Why Did We Need Docker?
|
||||||
|
|
||||||
|
You started a software project. You set up the development environment, installed the libraries, and everything works like clockwork. But when it comes to transferring the project to a teammate, the test environment, or the server, things go haywire. Because...
|
||||||
|
|
||||||
|
> "Dude, it works for you, but it doesn't work for me."
|
||||||
|
|
||||||
|
This is a classic scenario we've all experienced. Because every machine is different: the operating system is different, library versions are different, configurations are different... That is, the execution of the application is not only related to the software, but also to the **environment** it runs in.
|
||||||
|
|
||||||
|
That's why **we needed tools like Docker**. Because software is not just code; its dependencies, the system it runs on, its settings, ports, environment variables... are all a whole. Docker wraps this integrity in isolated boxes we call "containers" and ensures it works the same way everywhere.
|
||||||
|
|
||||||
|
## What Does Docker Do? — Think of It as a Magical Chest
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Docker takes your software, puts everything it needs to run next to it (libraries, environment settings, services, etc.), and packages them inside a container. This container runs the same way no matter where you take it.
|
||||||
|
|
||||||
|
Think of it like this:
|
||||||
|
|
||||||
|
* Developer computer
|
||||||
|
* Test server
|
||||||
|
* Production environment
|
||||||
|
* Cloud providers
|
||||||
|
|
||||||
|
Thanks to Docker, they all see the same environment. Because everything is inside the container, it is not affected by whatever is on your computer.
|
||||||
|
|
||||||
|
And best of all: Docker is extremely lightweight. It is not heavy like virtual machines. It starts in seconds and consumes very, very few resources. So it is fast, portable, and reliable. **A magical chest.**
|
||||||
|
|
||||||
|
## What is a Container? How is it Different from a Virtual Machine?
|
||||||
|
|
||||||
|
The answer to this question helps us better understand why Docker has created such a revolution.
|
||||||
|
|
||||||
|
### Virtual Machine (VM):
|
||||||
|
|
||||||
|
* Runs a full operating system (e.g. Ubuntu).
|
||||||
|
* It is heavy, taking up plenty of space from RAM and disk.
|
||||||
|
* Takes time to boot.
|
||||||
|
|
||||||
|
### Docker Container:
|
||||||
|
|
||||||
|
* Contains only your application and what it needs to run.
|
||||||
|
* Shares the host system's kernel.
|
||||||
|
* Is much lighter and starts quickly.
|
||||||
|
* Multiple containers can easily run on the same machine.
|
||||||
|
|
||||||
|
## 🐳 Running Our First Container with Docker
|
||||||
|
|
||||||
|
Theory is nice, but without practice, everything remains a bit meaningless. Now let's start by running our first container on a system with Docker installed (it doesn't matter if it's Windows, Mac, or Linux).
|
||||||
|
|
||||||
|
### 1. Is Docker Installed?
|
||||||
|
|
||||||
|
As a first step, check if Docker is installed on your system. Type this command in your terminal or command line:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker --version
|
||||||
|
```
|
||||||
|
|
||||||
|
If it's not installed, you can download and install Docker Desktop [from here](https://www.docker.com/products/docker-desktop/). You might need to restart your computer after installation.
|
||||||
|
|
||||||
|
### 2. First Docker Command: Hello World
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run hello-world
|
||||||
|
```
|
||||||
|
|
||||||
|
If you ask what this does:
|
||||||
|
|
||||||
|
* Pulls an image named `hello-world` from Docker Hub.
|
||||||
|
* Runs this image.
|
||||||
|
* It gives you a "Hello from Docker!" message in the terminal. **Did it?**
|
||||||
|
|
||||||
|
### 3. Creating a Dockerfile for Our Own Application
|
||||||
|
|
||||||
|
Now let's run a simple Python application inside Docker.
|
||||||
|
|
||||||
|
Our `app.py` file:
|
||||||
|
|
||||||
|
```python
|
||||||
|
print("Hello from inside Docker!")
|
||||||
|
```
|
||||||
|
|
||||||
|
`Dockerfile`:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# We told it to use Python as the base image.
|
||||||
|
FROM python:3.10-slim
|
||||||
|
|
||||||
|
# We set the working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# We copied the code file into the container
|
||||||
|
COPY app.py .
|
||||||
|
|
||||||
|
# What will be run when the container starts?
|
||||||
|
CMD ["python", "app.py"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build the Docker image:
|
||||||
|
|
||||||
|
Write this to the terminal:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t hello-world-python .
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the application:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run hello-world-python
|
||||||
|
```
|
||||||
|
|
||||||
|
And boom! You will see this in the terminal:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Hello from inside Docker!
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. So What Happened?
|
||||||
|
|
||||||
|
* Docker created a mini-system based on Python.
|
||||||
|
* It copied your `.py` file into it.
|
||||||
|
* And it ran this as a small, portable container.
|
||||||
|
|
||||||
|
That's it! Now your application ran independently of the system, **inside a Docker container**. No need to set up a special environment for you. Wherever you run it, the result will be the same.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Conclusion — Docker is Not Just a Tool, It's a Habit (In My Opinion)
|
||||||
|
|
||||||
|
Docker is not just a "tool" in modern software development processes; **it's a way of thinking and working**. If you want to run your applications in isolated environments, manage dependencies, and eliminate problems like "it works for me but broke on the server," Docker will be your biggest helper.
|
||||||
|
|
||||||
|
### Why is Docker Loved So Much?
|
||||||
|
|
||||||
|
* It works the same way in every environment.
|
||||||
|
* It provides harmony in teamwork.
|
||||||
|
* Makes it easy to move applications.
|
||||||
|
* Speeds up automation processes.
|
||||||
|
* Offers full support from local development to production.
|
||||||
|
|
||||||
|
And best of all? It's fun to learn and fast to use.
|
||||||
|
Once you get used to it, you'll want to add Docker support to all your projects. Because it gives freedom. Because it solves system complexity for you.
|
||||||
|
|
||||||
|
If you have read this article this far, you have stepped into the world of Docker. Now all you have to do is try and learn. Thanks for reading, see you.
|
||||||
+7
-2
@@ -12,6 +12,7 @@ export type BlogDetail = {
|
|||||||
excerpt: string;
|
excerpt: string;
|
||||||
coverImage: string;
|
coverImage: string;
|
||||||
markdown: string;
|
markdown: string;
|
||||||
|
lang: "tr" | "en";
|
||||||
};
|
};
|
||||||
|
|
||||||
const BLOG_CONTENT_DIR = path.join(process.cwd(), "content", "blog");
|
const BLOG_CONTENT_DIR = path.join(process.cwd(), "content", "blog");
|
||||||
@@ -40,10 +41,11 @@ function mapMarkdownToBlogDetail(fileName: string, raw: string): BlogDetail {
|
|||||||
excerpt: toSafeString(parsed.data.excerpt, ""),
|
excerpt: toSafeString(parsed.data.excerpt, ""),
|
||||||
coverImage: toSafeString(parsed.data.coverImage, "/news/design.svg"),
|
coverImage: toSafeString(parsed.data.coverImage, "/news/design.svg"),
|
||||||
markdown: parsed.content.trim(),
|
markdown: parsed.content.trim(),
|
||||||
|
lang: toSafeString(parsed.data.lang, "tr") as "tr" | "en",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listBlogDetails(): Promise<BlogDetail[]> {
|
export async function listBlogDetails(locale?: string): Promise<BlogDetail[]> {
|
||||||
let files: string[] = [];
|
let files: string[] = [];
|
||||||
try {
|
try {
|
||||||
files = await fs.readdir(BLOG_CONTENT_DIR);
|
files = await fs.readdir(BLOG_CONTENT_DIR);
|
||||||
@@ -60,7 +62,10 @@ export async function listBlogDetails(): Promise<BlogDetail[]> {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return posts.sort((a, b) => a.slug.localeCompare(b.slug));
|
const targetLang = locale || "tr";
|
||||||
|
return posts
|
||||||
|
.filter((post) => post.lang === targetLang)
|
||||||
|
.sort((a, b) => a.slug.localeCompare(b.slug));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getBlogDetailBySlug(slug: string): Promise<BlogDetail | null> {
|
export async function getBlogDetailBySlug(slug: string): Promise<BlogDetail | null> {
|
||||||
|
|||||||
+6
-5
@@ -54,8 +54,8 @@ function normalizeCategory(value: string) {
|
|||||||
return value.trim().toLocaleLowerCase();
|
return value.trim().toLocaleLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllBlogArticles(): Promise<BlogArticleItem[]> {
|
export async function getAllBlogArticles(locale?: string): Promise<BlogArticleItem[]> {
|
||||||
const posts = await listBlogDetails();
|
const posts = await listBlogDetails(locale);
|
||||||
|
|
||||||
const articles = posts.map((post) => ({
|
const articles = posts.map((post) => ({
|
||||||
id: post.slug,
|
id: post.slug,
|
||||||
@@ -73,8 +73,8 @@ export async function getAllBlogArticles(): Promise<BlogArticleItem[]> {
|
|||||||
return sortByDateDesc(articles);
|
return sortByDateDesc(articles);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getHomeBlogNews(limit = 3) {
|
export async function getHomeBlogNews(locale?: string, limit = 3) {
|
||||||
const articles = await getAllBlogArticles();
|
const articles = await getAllBlogArticles(locale);
|
||||||
|
|
||||||
return articles.slice(0, limit).map((item) => ({
|
return articles.slice(0, limit).map((item) => ({
|
||||||
id: `home-news-${item.slug}`,
|
id: `home-news-${item.slug}`,
|
||||||
@@ -87,12 +87,13 @@ export async function getHomeBlogNews(limit = 3) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getBlogPageData(
|
export async function getBlogPageData(
|
||||||
|
locale?: string,
|
||||||
page = 1,
|
page = 1,
|
||||||
pageSize = 12,
|
pageSize = 12,
|
||||||
selectedCategoryParam?: string,
|
selectedCategoryParam?: string,
|
||||||
searchQueryParam?: string,
|
searchQueryParam?: string,
|
||||||
): Promise<BlogPageData> {
|
): Promise<BlogPageData> {
|
||||||
const articles = await getAllBlogArticles();
|
const articles = await getAllBlogArticles(locale);
|
||||||
const categories = BLOG_CATEGORIES;
|
const categories = BLOG_CATEGORIES;
|
||||||
const categoryByNormalized = new Map(
|
const categoryByNormalized = new Map(
|
||||||
categories.map((category) => [normalizeCategory(category), category]),
|
categories.map((category) => [normalizeCategory(category), category]),
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ slug: "${slug}"
|
|||||||
excerpt: "${safeExcerpt}"
|
excerpt: "${safeExcerpt}"
|
||||||
coverImage: "${localCoverImageUrl}"
|
coverImage: "${localCoverImageUrl}"
|
||||||
canonicalUrl: "${url}"
|
canonicalUrl: "${url}"
|
||||||
|
lang: "tr"
|
||||||
---
|
---
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|||||||
Reference in New Issue
Block a user