feat: Add flexible category handling and fix category page

This commit is contained in:
Daniel LaForce 2025-04-23 14:30:17 -06:00
parent b340d3fe78
commit ad2e0acab2
4 changed files with 442 additions and 1 deletions

View File

@ -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.

View File

@ -39,6 +39,16 @@ const baseSchema = z.object({
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),
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'),
tags: z.union([z.array(z.string()), z.null()]).optional().default([]),
draft: z.boolean().optional().default(false),
@ -49,7 +59,14 @@ const baseSchema = z.object({
github: z.string().optional(),
live: 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
const postsCollection = defineCollection({

View File

@ -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.

View File

@ -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>