feat: Add flexible category handling and fix category page
This commit is contained in:
parent
b340d3fe78
commit
ad2e0acab2
|
@ -0,0 +1,65 @@
|
||||||
|
---
|
||||||
|
title: 'Getting Started with Infrastructure as Code'
|
||||||
|
description: 'Learn the basics of Infrastructure as Code and how to start using it in your projects.'
|
||||||
|
pubDate: '2023-11-15'
|
||||||
|
heroImage: '/images/placeholders/infrastructure.jpg'
|
||||||
|
categories: ['Infrastructure', 'DevOps']
|
||||||
|
tags: ['terraform', 'infrastructure', 'cloud', 'automation']
|
||||||
|
minutesRead: '5 min'
|
||||||
|
---
|
||||||
|
|
||||||
|
# Getting Started with Infrastructure as Code
|
||||||
|
|
||||||
|
Infrastructure as Code (IaC) is a key DevOps practice that involves managing and provisioning infrastructure through code instead of manual processes. This approach brings the same rigor, transparency, and version control to infrastructure that developers have long applied to application code.
|
||||||
|
|
||||||
|
## Why Infrastructure as Code?
|
||||||
|
|
||||||
|
IaC offers numerous benefits for modern DevOps teams:
|
||||||
|
|
||||||
|
- **Consistency**: Infrastructure deployments become reproducible and standardized
|
||||||
|
- **Version Control**: Track changes to your infrastructure just like application code
|
||||||
|
- **Automation**: Reduce manual errors and increase deployment speed
|
||||||
|
- **Documentation**: Your code becomes self-documenting
|
||||||
|
- **Testing**: Infrastructure can be tested before deployment
|
||||||
|
|
||||||
|
## Popular IaC Tools
|
||||||
|
|
||||||
|
There are several powerful tools for implementing IaC:
|
||||||
|
|
||||||
|
1. **Terraform**: Cloud-agnostic, works with multiple providers
|
||||||
|
2. **AWS CloudFormation**: Specific to AWS infrastructure
|
||||||
|
3. **Azure Resource Manager**: Microsoft's native IaC solution
|
||||||
|
4. **Google Cloud Deployment Manager**: For Google Cloud resources
|
||||||
|
5. **Pulumi**: Uses general-purpose programming languages
|
||||||
|
|
||||||
|
## Basic Terraform Example
|
||||||
|
|
||||||
|
Here's a simple example of Terraform code that provisions an AWS EC2 instance:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
provider "aws" {
|
||||||
|
region = "us-west-2"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "web_server" {
|
||||||
|
ami = "ami-0c55b159cbfafe1f0"
|
||||||
|
instance_type = "t2.micro"
|
||||||
|
|
||||||
|
tags = {
|
||||||
|
Name = "Web Server"
|
||||||
|
Environment = "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
To begin your IaC journey:
|
||||||
|
|
||||||
|
1. Choose a tool that fits your infrastructure needs
|
||||||
|
2. Start small with a simple resource
|
||||||
|
3. Learn about state management
|
||||||
|
4. Implement CI/CD for your infrastructure code
|
||||||
|
5. Consider using modules for reusability
|
||||||
|
|
||||||
|
Infrastructure as Code transforms how teams provision and manage resources, enabling more reliable, consistent deployments while reducing overhead and errors.
|
|
@ -39,6 +39,16 @@ const baseSchema = z.object({
|
||||||
pubDate: z.union([z.string(), z.date(), z.null()]).optional().default(() => new Date()).transform(customDateParser),
|
pubDate: z.union([z.string(), z.date(), z.null()]).optional().default(() => new Date()).transform(customDateParser),
|
||||||
updatedDate: z.union([z.string(), z.date(), z.null()]).optional().transform(val => val ? customDateParser(val) : undefined),
|
updatedDate: z.union([z.string(), z.date(), z.null()]).optional().transform(val => val ? customDateParser(val) : undefined),
|
||||||
heroImage: z.string().optional().nullable(),
|
heroImage: z.string().optional().nullable(),
|
||||||
|
// Add categories array that falls back to the single category field
|
||||||
|
categories: z.union([
|
||||||
|
z.array(z.string()),
|
||||||
|
z.string().transform(val => [val]),
|
||||||
|
z.null()
|
||||||
|
]).optional().transform(val => {
|
||||||
|
if (val === null || val === undefined) return ['Uncategorized'];
|
||||||
|
return val;
|
||||||
|
}),
|
||||||
|
// Keep the original category field for backward compatibility
|
||||||
category: z.string().optional().default('Uncategorized'),
|
category: z.string().optional().default('Uncategorized'),
|
||||||
tags: z.union([z.array(z.string()), z.null()]).optional().default([]),
|
tags: z.union([z.array(z.string()), z.null()]).optional().default([]),
|
||||||
draft: z.boolean().optional().default(false),
|
draft: z.boolean().optional().default(false),
|
||||||
|
@ -49,7 +59,14 @@ const baseSchema = z.object({
|
||||||
github: z.string().optional(),
|
github: z.string().optional(),
|
||||||
live: z.string().optional(),
|
live: z.string().optional(),
|
||||||
technologies: z.array(z.string()).optional(),
|
technologies: z.array(z.string()).optional(),
|
||||||
}).passthrough(); // Allow any other frontmatter properties
|
}).passthrough() // Allow any other frontmatter properties
|
||||||
|
.transform(data => {
|
||||||
|
// If categories isn't set but category is, use category value to populate categories
|
||||||
|
if ((!data.categories || data.categories.length === 0) && data.category) {
|
||||||
|
data.categories = [data.category];
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
|
||||||
// Define collections using the same base schema
|
// Define collections using the same base schema
|
||||||
const postsCollection = defineCollection({
|
const postsCollection = defineCollection({
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
---
|
||||||
|
title: 'Getting Started with Infrastructure as Code'
|
||||||
|
description: 'Learn the basics of Infrastructure as Code and how to start using it in your projects.'
|
||||||
|
pubDate: '2023-11-15'
|
||||||
|
heroImage: '/images/placeholders/infrastructure.jpg'
|
||||||
|
categories: ['Infrastructure', 'DevOps']
|
||||||
|
tags: ['terraform', 'infrastructure', 'cloud', 'automation']
|
||||||
|
minutesRead: '5 min'
|
||||||
|
---
|
||||||
|
|
||||||
|
# Getting Started with Infrastructure as Code
|
||||||
|
|
||||||
|
Infrastructure as Code (IaC) is a key DevOps practice that involves managing and provisioning infrastructure through code instead of manual processes. This approach brings the same rigor, transparency, and version control to infrastructure that developers have long applied to application code.
|
||||||
|
|
||||||
|
## Why Infrastructure as Code?
|
||||||
|
|
||||||
|
IaC offers numerous benefits for modern DevOps teams:
|
||||||
|
|
||||||
|
- **Consistency**: Infrastructure deployments become reproducible and standardized
|
||||||
|
- **Version Control**: Track changes to your infrastructure just like application code
|
||||||
|
- **Automation**: Reduce manual errors and increase deployment speed
|
||||||
|
- **Documentation**: Your code becomes self-documenting
|
||||||
|
- **Testing**: Infrastructure can be tested before deployment
|
||||||
|
|
||||||
|
## Popular IaC Tools
|
||||||
|
|
||||||
|
There are several powerful tools for implementing IaC:
|
||||||
|
|
||||||
|
1. **Terraform**: Cloud-agnostic, works with multiple providers
|
||||||
|
2. **AWS CloudFormation**: Specific to AWS infrastructure
|
||||||
|
3. **Azure Resource Manager**: Microsoft's native IaC solution
|
||||||
|
4. **Google Cloud Deployment Manager**: For Google Cloud resources
|
||||||
|
5. **Pulumi**: Uses general-purpose programming languages
|
||||||
|
|
||||||
|
## Basic Terraform Example
|
||||||
|
|
||||||
|
Here's a simple example of Terraform code that provisions an AWS EC2 instance:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
provider "aws" {
|
||||||
|
region = "us-west-2"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "web_server" {
|
||||||
|
ami = "ami-0c55b159cbfafe1f0"
|
||||||
|
instance_type = "t2.micro"
|
||||||
|
|
||||||
|
tags = {
|
||||||
|
Name = "Web Server"
|
||||||
|
Environment = "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
To begin your IaC journey:
|
||||||
|
|
||||||
|
1. Choose a tool that fits your infrastructure needs
|
||||||
|
2. Start small with a simple resource
|
||||||
|
3. Learn about state management
|
||||||
|
4. Implement CI/CD for your infrastructure code
|
||||||
|
5. Consider using modules for reusability
|
||||||
|
|
||||||
|
Infrastructure as Code transforms how teams provision and manage resources, enabling more reliable, consistent deployments while reducing overhead and errors.
|
|
@ -0,0 +1,294 @@
|
||||||
|
---
|
||||||
|
// src/pages/categories/[category].astro
|
||||||
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||||
|
import { getCollection } from 'astro:content';
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
try {
|
||||||
|
// Get posts from the posts collection
|
||||||
|
const allPosts = await getCollection('posts', ({ data }) => {
|
||||||
|
// Exclude draft posts in production
|
||||||
|
return import.meta.env.PROD ? !data.draft : true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract all categories from posts
|
||||||
|
const allCategories = new Set<string>();
|
||||||
|
|
||||||
|
allPosts.forEach(post => {
|
||||||
|
// Handle both single category and categories array
|
||||||
|
if (post.data.categories && Array.isArray(post.data.categories)) {
|
||||||
|
post.data.categories.forEach(cat => allCategories.add(cat));
|
||||||
|
} else if (post.data.category) {
|
||||||
|
allCategories.add(post.data.category);
|
||||||
|
} else {
|
||||||
|
allCategories.add('Uncategorized');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert to array and sort alphabetically
|
||||||
|
const uniqueCategories = Array.from(allCategories).sort();
|
||||||
|
|
||||||
|
// If there are no categories, provide a default path
|
||||||
|
if (uniqueCategories.length === 0) {
|
||||||
|
return [{
|
||||||
|
params: { category: 'general' },
|
||||||
|
props: { posts: [] }
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a path for each category
|
||||||
|
return uniqueCategories.map(category => {
|
||||||
|
// Filter posts for this category
|
||||||
|
const filteredPosts = allPosts.filter(post => {
|
||||||
|
if (post.data.categories && Array.isArray(post.data.categories)) {
|
||||||
|
return post.data.categories.includes(category);
|
||||||
|
}
|
||||||
|
return post.data.category === category;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
params: { category },
|
||||||
|
props: { posts: filteredPosts }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generating category pages:', error);
|
||||||
|
// Fallback to ensure build doesn't fail
|
||||||
|
return [{
|
||||||
|
params: { category: 'general' },
|
||||||
|
props: { posts: [] }
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { category } = Astro.params;
|
||||||
|
const { posts } = Astro.props;
|
||||||
|
|
||||||
|
// Format date function
|
||||||
|
const formatDate = (dateStr) => {
|
||||||
|
if (!dateStr) return '';
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
|
||||||
|
};
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title={`${category} | LaForce IT Blog`} description={`Articles in the ${category} category`}>
|
||||||
|
<div class="container">
|
||||||
|
<div class="category-header">
|
||||||
|
<h1>Posts in category: <span class="category-name">{category}</span></h1>
|
||||||
|
<p class="category-description">Browse {posts.length} {posts.length === 1 ? 'article' : 'articles'} in this category</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{posts.length > 0 ? (
|
||||||
|
<div class="posts-grid">
|
||||||
|
{posts.map((post) => (
|
||||||
|
<article class="post-card">
|
||||||
|
<img
|
||||||
|
width={720}
|
||||||
|
height={360}
|
||||||
|
src={post.data.heroImage || "/images/placeholders/default.jpg"}
|
||||||
|
alt=""
|
||||||
|
class="post-image"
|
||||||
|
/>
|
||||||
|
<div class="post-content">
|
||||||
|
<time datetime={post.data.pubDate}>{formatDate(post.data.pubDate)}</time>
|
||||||
|
<h2 class="post-title">
|
||||||
|
<a href={`/posts/${post.slug}/`}>{post.data.title}</a>
|
||||||
|
</h2>
|
||||||
|
<p class="post-excerpt">{post.data.description || post.data.excerpt || ''}</p>
|
||||||
|
<div class="post-meta">
|
||||||
|
<span class="reading-time">{post.data.readTime || '5 min'} read</span>
|
||||||
|
{post.data.tags && post.data.tags.length > 0 && (
|
||||||
|
<div class="post-tags">
|
||||||
|
{post.data.tags.slice(0, 3).map((tag) => (
|
||||||
|
<a href={`/tag/${tag}`} class="tag-link">
|
||||||
|
{tag}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div class="empty-state">
|
||||||
|
<p>No posts in this category yet. Check back soon!</p>
|
||||||
|
<a href="/blog" class="back-to-blog">Browse all posts</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</BaseLayout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
padding: 2rem 0;
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 var(--container-padding, 1.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header {
|
||||||
|
text-align: center;
|
||||||
|
margin: 2rem 0 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-name {
|
||||||
|
background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-description {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: var(--font-size-lg, 1.125rem);
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.posts-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||||
|
gap: 2rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-card {
|
||||||
|
background: var(--card-bg);
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-card:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-bottom: 1px solid var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content {
|
||||||
|
padding: 1.5rem;
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-content time {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: var(--font-size-sm, 0.875rem);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-title {
|
||||||
|
font-size: var(--font-size-xl, 1.25rem);
|
||||||
|
margin: 0.5rem 0 1rem;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-title a {
|
||||||
|
color: var(--text-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-title a:hover {
|
||||||
|
color: var(--accent-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-excerpt {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: var(--font-size-md, 1rem);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reading-time {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: var(--font-size-sm, 0.875rem);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-link {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
background: rgba(56, 189, 248, 0.1);
|
||||||
|
border-radius: 20px;
|
||||||
|
color: var(--accent-primary);
|
||||||
|
font-size: var(--font-size-xs, 0.75rem);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag-link:hover {
|
||||||
|
background: rgba(56, 189, 248, 0.2);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 4rem 2rem;
|
||||||
|
background: var(--card-bg);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state p {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-blog {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
background: var(--accent-primary);
|
||||||
|
color: var(--bg-primary);
|
||||||
|
border-radius: 6px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-to-blog:hover {
|
||||||
|
background: var(--accent-secondary);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.posts-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-header {
|
||||||
|
margin: 1rem 0 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in New Issue