diff --git a/astro.config.mjs b/astro.config.mjs index ab6552d..31edf62 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -7,7 +7,7 @@ import tailwind from '@astrojs/tailwind'; // https://astro.build/config export default defineConfig({ - site: 'https://laforceit.blog', + site: 'https://laforceit-blog.pages.dev', // Your current Cloudflare site output: 'static', // adapter: cloudflare(), // Commented out for local development integrations: [ @@ -17,10 +17,14 @@ export default defineConfig({ ], markdown: { shikiConfig: { - theme: 'dracula', + theme: 'one-dark-pro', wrap: true }, remarkPlugins: [], rehypePlugins: [] + }, + compressHTML: false, // Disable HTML compression to avoid parsing errors + build: { + format: 'file', // Use 'file' instead of 'directory' format } }); \ No newline at end of file diff --git a/src/components/Header.astro b/src/components/Header.astro index 1313894..cd066a7 100644 --- a/src/components/Header.astro +++ b/src/components/Header.astro @@ -379,12 +379,13 @@ const navItems = [ \ No newline at end of file diff --git a/src/content/blog/getting-started.md b/src/content/blog/getting-started.md new file mode 100644 index 0000000..80629d7 --- /dev/null +++ b/src/content/blog/getting-started.md @@ -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. \ No newline at end of file diff --git a/src/content/config.ts b/src/content/config.ts index 15fec0b..7dd588e 100644 --- a/src/content/config.ts +++ b/src/content/config.ts @@ -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({ diff --git a/src/content/posts/getting-started.md b/src/content/posts/getting-started.md new file mode 100644 index 0000000..80629d7 --- /dev/null +++ b/src/content/posts/getting-started.md @@ -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. \ No newline at end of file diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 0cf6737..d2f843b 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -23,6 +23,18 @@ const { {title} + + + @@ -44,6 +56,9 @@ const { + + + diff --git a/src/pages/blog/[slug].astro b/src/pages/blog/[slug].astro index 48a119f..74b48bb 100644 --- a/src/pages/blog/[slug].astro +++ b/src/pages/blog/[slug].astro @@ -1,38 +1,264 @@ --- -import { getCollection, getEntryBySlug } from 'astro:content'; -import BlogPost from '../../layouts/BlogPost.astro'; +// src/pages/blog/[slug].astro +import { getCollection } from 'astro:content'; +import BaseLayout from '../../layouts/BaseLayout.astro'; +// Required getStaticPaths function for dynamic routes export async function getStaticPaths() { - const posts = await getCollection('blog'); - return posts.map(post => ({ - params: { slug: post.slug }, - props: { post }, - })); + try { + // Try first from 'blog' collection (auto-generated) + let allPosts = []; + try { + allPosts = await getCollection('blog', ({ data }) => { + return import.meta.env.PROD ? !data.draft : true; + }); + } catch (e) { + console.log('Blog collection not found, trying posts'); + } + + // If that fails or is empty, try 'posts' collection + if (allPosts.length === 0) { + allPosts = await getCollection('posts', ({ data }) => { + return import.meta.env.PROD ? !data.draft : true; + }); + } + + return allPosts.map(post => ({ + params: { slug: post.slug }, + props: { post }, + })); + } catch (error) { + console.error('Error fetching posts:', error); + // Return empty array if both collections don't exist or are empty + return []; + } } +// Get the post from props const { post } = Astro.props; -const { Content } = await post.render(); -// Handle undefined or null values -const title = post.data.title || ''; -const description = post.data.description || ''; -const pubDate = post.data.pubDate ? new Date(post.data.pubDate) : new Date(); -const updatedDate = post.data.updatedDate ? new Date(post.data.updatedDate) : undefined; -const heroImage = post.data.heroImage || undefined; -const category = post.data.category || undefined; -const tags = post.data.tags || []; -const draft = post.data.draft || false; +// Format date helper +const formatDate = (date) => { + if (!date) return ''; + const d = new Date(date); + return d.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }); +}; + +// Generate datetime attribute safely +const getISODate = (date) => { + if (!date) return ''; + // Handle various date formats + try { + // If already a Date object + if (date instanceof Date) { + return date.toISOString(); + } + // If it's a string or number, convert to Date + return new Date(date).toISOString(); + } catch (error) { + // Fallback if date is invalid + console.error('Invalid date format:', date); + return ''; + } +}; + +// Get the Content component for rendering markdown +const { Content } = await post.render(); --- - - - \ No newline at end of file + +
+
+

{post.data.title}

+ +
+ + {post.data.heroImage && ( +
+ {post.data.title} +
+ )} + +
+
+ +
+ + +
+
+
+ + \ No newline at end of file diff --git a/src/pages/categories/[category].astro b/src/pages/categories/[category].astro index e69de29..2423ceb 100644 --- a/src/pages/categories/[category].astro +++ b/src/pages/categories/[category].astro @@ -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(); + + 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' }); +}; +--- + + +
+
+

Posts in category: {category}

+

Browse {posts.length} {posts.length === 1 ? 'article' : 'articles'} in this category

+
+ + {posts.length > 0 ? ( +
+ {posts.map((post) => ( +
+ +
+ +

+ {post.data.title} +

+

{post.data.description || post.data.excerpt || ''}

+ +
+
+ ))} +
+ ) : ( +
+

No posts in this category yet. Check back soon!

+ Browse all posts +
+ )} +
+
+ + \ No newline at end of file diff --git a/src/pages/configurations/[slug].astro b/src/pages/configurations/[slug].astro index e69de29..78aa7e8 100644 --- a/src/pages/configurations/[slug].astro +++ b/src/pages/configurations/[slug].astro @@ -0,0 +1,225 @@ +--- +// src/pages/configurations/[slug].astro +import { getCollection } from 'astro:content'; +import BaseLayout from '../../layouts/BaseLayout.astro'; + +// Required getStaticPaths function for dynamic routes +export async function getStaticPaths() { + try { + const configEntries = await getCollection('configurations', ({ data }) => { + // Filter out drafts in production + return import.meta.env.PROD ? !data.draft : true; + }); + + return configEntries.map(entry => ({ + params: { slug: entry.slug }, + props: { entry }, + })); + } catch (error) { + console.error('Error fetching configurations:', error); + // Return empty array if collection doesn't exist or is empty + return []; + } +} + +// Get the configuration from props +const { entry } = Astro.props; + +// Format date helper +const formatDate = (date) => { + if (!date) return ''; + const d = new Date(date); + return d.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }); +}; + +// Generate datetime attribute safely +const getISODate = (date) => { + if (!date) return ''; + // Handle various date formats + try { + // If already a Date object + if (date instanceof Date) { + return date.toISOString(); + } + // If it's a string or number, convert to Date + return new Date(date).toISOString(); + } catch (error) { + // Fallback if date is invalid + console.error('Invalid date format:', date); + return ''; + } +}; + +// Get the Content component for rendering markdown +const { Content } = await entry.render(); +--- + + +
+
+

{entry.data.title}

+ {entry.data.pubDate && } + {entry.data.updatedDate &&
Updated: {formatDate(entry.data.updatedDate)}
} +
+ +
+
+ +
+ + +
+
+
+ + \ No newline at end of file diff --git a/src/pages/projects/[slug].astro b/src/pages/projects/[slug].astro index e69de29..dc15838 100644 --- a/src/pages/projects/[slug].astro +++ b/src/pages/projects/[slug].astro @@ -0,0 +1,248 @@ +--- +// src/pages/projects/[slug].astro +import { getCollection } from 'astro:content'; +import BaseLayout from '../../layouts/BaseLayout.astro'; + +// Required getStaticPaths function for dynamic routes +export async function getStaticPaths() { + try { + const projectEntries = await getCollection('projects', ({ data }) => { + // Filter out drafts in production + return import.meta.env.PROD ? !data.draft : true; + }); + + return projectEntries.map(entry => ({ + params: { slug: entry.slug }, + props: { entry }, + })); + } catch (error) { + console.error('Error fetching projects:', error); + // Return empty array if collection doesn't exist or is empty + return []; + } +} + +// Get the project from props +const { entry } = Astro.props; + +// Format date helper +const formatDate = (date) => { + if (!date) return ''; + const d = new Date(date); + return d.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }); +}; + +// Generate datetime attribute safely +const getISODate = (date) => { + if (!date) return ''; + // Handle various date formats + try { + // If already a Date object + if (date instanceof Date) { + return date.toISOString(); + } + // If it's a string or number, convert to Date + return new Date(date).toISOString(); + } catch (error) { + // Fallback if date is invalid + console.error('Invalid date format:', date); + return ''; + } +}; + +// Get the Content component for rendering markdown +const { Content } = await entry.render(); +--- + + +
+
+

{entry.data.title}

+ {entry.data.pubDate && } + {entry.data.updatedDate &&
Updated: {formatDate(entry.data.updatedDate)}
} +
+ +
+
+ +
+ + +
+
+
+ + \ No newline at end of file diff --git a/src/pages/tag/[tag].astro b/src/pages/tag/[tag].astro index 3aebbc0..de4892a 100644 --- a/src/pages/tag/[tag].astro +++ b/src/pages/tag/[tag].astro @@ -1,255 +1,269 @@ -import { getCollection } from 'astro:content'; +--- +// src/pages/tag/[tag].astro +// Dynamic route for tag pages + import BaseLayout from '../../layouts/BaseLayout.astro'; +import { getCollection } from 'astro:content'; export async function getStaticPaths() { - const allPosts = await getCollection('posts'); - - // Get all unique tags from all posts - const uniqueTags = [...new Set(allPosts.flatMap(post => post.data.tags || []))]; - - // Create a page for each tag - return uniqueTags.map(tag => { - // Filter posts that have this tag - const filteredPosts = allPosts.filter(post => - post.data.tags && post.data.tags.includes(tag) - ); - + const allPosts = await getCollection('blog'); + const uniqueTags = [...new Set(allPosts.map((post) => post.data.tags).flat())]; + + return uniqueTags.map((tag) => { + const filteredPosts = allPosts.filter((post) => post.data.tags.includes(tag)); return { params: { tag }, - props: { posts: filteredPosts, tag }, + props: { posts: filteredPosts }, }; }); } -const { posts, tag } = Astro.props; +const { tag } = Astro.params; +const { posts } = Astro.props; -// Sort posts by date +// Format date +const formatDate = (dateStr) => { + const date = new Date(dateStr); + return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); +}; + +// Sort posts by date (newest first) const sortedPosts = posts.sort((a, b) => { - const dateA = a.data.pubDate ? new Date(a.data.pubDate) : new Date(0); - const dateB = b.data.pubDate ? new Date(b.data.pubDate) : new Date(0); + const dateA = new Date(a.data.pubDate); + const dateB = new Date(b.data.pubDate); return dateB.getTime() - dateA.getTime(); }); --- -
-
-

Posts tagged with #{tag}

-

- Browse all {sortedPosts.length} articles related to this topic -

- View all tags -
+
+
+

Posts tagged with {tag}

+

Explore {sortedPosts.length} {sortedPosts.length === 1 ? 'article' : 'articles'} related to {tag}

+
-
+
{sortedPosts.map((post) => (
- {/* Temporarily removed conditional image rendering for debugging */} +
- -

+ +

{post.data.title} - {post.data.draft && Draft} -

+

{post.data.description}

-
))}
-
+ + + + + + + View all tags + +
\ No newline at end of file diff --git a/src/styles/theme.css b/src/styles/theme.css new file mode 100644 index 0000000..c20ca55 --- /dev/null +++ b/src/styles/theme.css @@ -0,0 +1,82 @@ +/* Theme Variables - Dark/Light Mode Support */ + +/* Dark theme (default) */ +html { + /* Keep the default dark theme as defined in BaseLayout */ +} + +/* Light theme */ +html.light-mode { + /* Primary Colors */ + --bg-primary: #f8fafc; + --bg-secondary: #f1f5f9; + --bg-tertiary: #e2e8f0; + --bg-code: #f1f5f9; + --text-primary: #0f172a; + --text-secondary: #334155; + --text-tertiary: #64748b; + + /* Accent Colors remain the same for brand consistency */ + + /* Glow Effects - lighter for light mode */ + --glow-primary: rgba(6, 182, 212, 0.1); + --glow-secondary: rgba(59, 130, 246, 0.1); + --glow-tertiary: rgba(139, 92, 246, 0.1); + + /* Border Colors */ + --border-primary: rgba(0, 0, 0, 0.1); + --border-secondary: rgba(0, 0, 0, 0.05); + + /* Card Background */ + --card-bg: rgba(255, 255, 255, 0.8); + --card-border: rgba(56, 189, 248, 0.3); /* Slightly stronger border */ + + /* UI Element Colors */ + --ui-element: #e2e8f0; + --ui-element-hover: #cbd5e1; +} + +/* Background adjustments for light mode */ +html.light-mode body { + background-image: + radial-gradient(circle at 20% 35%, rgba(6, 182, 212, 0.05) 0%, transparent 50%), + radial-gradient(circle at 75% 15%, rgba(59, 130, 246, 0.05) 0%, transparent 45%), + radial-gradient(circle at 85% 70%, rgba(139, 92, 246, 0.05) 0%, transparent 40%); +} + +/* Adding light mode grid overlay */ +html.light-mode body::before { + background-image: + linear-gradient(rgba(15, 23, 42, 0.03) 1px, transparent 1px), + linear-gradient(90deg, rgba(15, 23, 42, 0.03) 1px, transparent 1px); +} + +/* Theme transition for smooth switching */ +html, body, * { + transition: + background-color 0.3s ease, + color 0.3s ease, + border-color 0.3s ease, + box-shadow 0.3s ease; +} + +/* Knowledge Graph light mode adjustments */ +html.light-mode .graph-container { + background: rgba(248, 250, 252, 0.6); +} + +html.light-mode .graph-loading { + background: rgba(241, 245, 249, 0.7); +} + +html.light-mode .graph-filters { + background: rgba(241, 245, 249, 0.7); +} + +html.light-mode .graph-legend { + background: rgba(241, 245, 249, 0.7); +} + +html.light-mode .node-details { + background: rgba(248, 250, 252, 0.9); +} \ No newline at end of file