feat: initial commit

This commit is contained in:
2023-09-02 22:58:42 +02:00
commit bb0950208e
70 changed files with 22649 additions and 0 deletions

42
src/pages/404.astro Normal file
View File

@@ -0,0 +1,42 @@
---
import { SITE } from "@config";
import Layout from "@layouts/Layout.astro";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import LinkButton from "@components/LinkButton.astro";
---
<Layout title={`404 Not Found | ${SITE.title}`}>
<Header />
<main id="main-content">
<div class="not-found-wrapper">
<h1 aria-label="404 Not Found">404</h1>
<span aria-hidden="true">¯\_(ツ)_/¯</span>
<p>Page Not Found</p>
<LinkButton
href="/"
className="my-6 underline decoration-dashed underline-offset-8 text-lg"
>
Go back home
</LinkButton>
</div>
</main>
<Footer />
</Layout>
<style>
#main-content {
@apply mx-auto flex max-w-3xl flex-1 items-center justify-center;
}
.not-found-wrapper {
@apply mb-14 flex flex-col items-center justify-center;
}
.not-found-wrapper h1 {
@apply text-9xl font-bold text-skin-accent;
}
.not-found-wrapper p {
@apply mt-4 text-2xl sm:text-3xl;
}
</style>

View File

@@ -0,0 +1,18 @@
import { getCollection } from "astro:content";
import generateOgImage from "@utils/generateOgImage";
import type { APIRoute } from "astro";
export const get: APIRoute = async ({ params }) => ({
body: await generateOgImage(params.ogTitle),
});
const postImportResult = await getCollection("blog", ({ data }) => !data.draft);
const posts = Object.values(postImportResult);
export function getStaticPaths() {
return posts
.filter(({ data }) => !data.ogImage)
.map(({ data }) => ({
params: { ogTitle: data.title },
}));
}

65
src/pages/experience.md Normal file
View File

@@ -0,0 +1,65 @@
---
layout: ../layouts/AboutLayout.astro
title: "Experience"
---
### DevOps Engineer, Materna SE
**since 2023**
As a key globally active IT service provider, Materna advise and assist you in all aspects of digitization and provide tailor-made technologies for agile, flexible and secure IT.
- **Infrastructure as Code (IaC)**:
- Develop and maintain infrastructure as code scripts using tools like Terraform, Ansible, or CloudFormation to automate the provisioning of infrastructure resources.
- **Continuous Integration (CI) and Continuous Deployment (CD)**:
- Implement and manage CI/CD pipelines using tools like Jenkins, Travis CI, or GitLab CI to automate the software delivery process.
- **Containerization and Orchestration**:
- Work with Docker containers and container orchestration platforms like Kubernetes to improve scalability and resource utilization.
- **Monitoring and Logging**:
- Set up monitoring and logging solutions (e.g., Prometheus, ELK Stack) to track application performance, identify issues, and troubleshoot problems proactively.
- **Collaboration and Communication**:
- Foster collaboration between development and operations teams, ensuring effective communication and knowledge sharing.
- **Infrastructure Optimization**:
- Analyze and optimize infrastructure costs, resource utilization, and performance to achieve cost-efficiency and scalability.
- **Troubleshooting and Support**:
- Respond to incidents, diagnose problems, and provide support to ensure system reliability and availability.
### DevOps Engineer, Apozin GmbH
**until 2023**
Apozin turns visions into a competitive advantage. Our team of pharmacists, PTA's, graphic designers, web designers, sales professionals, marketing specialists, and programmers realize holistic concepts that we constantly evolve and improve for our clients.
- Operation and design of Kubernetes clusters at multiple locations
- Design and implementation of backup strategies
- Deployment of various services (including HAProxy, MariaDB, MongoDB, Elasticsearch, NGINX)
- Design and operation of comprehensive monitoring solutions (Zabbix, Grafana, Prometheus, Graylog)
- Design and setup of build pipelines with Jenkins, Docker, and FluxCD
- Administration of various servers in different environments (Google Cloud, Hetzner, AWS, Digital Ocean, Hosting.de)
### Fullstack .Net Developer, prointernet
**until 2019**
Agency for internet and design founded in 1998, established in Kastellaun in the Hunsrück region, operating worldwide, and at home on the internet. A team of designers, developers, and consultants who love what they do.
- Development of web applications (C#, Dotnet, JS)
- Design of websites (Composite C1)
- Company Website
## Projects
### DevOps Engineer, Amamed
**until 2023**
Just right for your pharmacy! amamed is the only digital solution on the market that puts your pharmacy at the center and makes you fully equipped, secure, and flexible online.
- Provision of various services (including reverse proxies, databases, load balancers)
- Operation of Docker Swarm clusters
- Product Website
### DevOps Engineer, deineApotheke
**until 2021**
"deine Apotheke" supports the pharmacies in your neighborhood and paves the way for you to access pharmacy services: through our app, you can select your pharmacy and pre-order medications, even with a prescription.
- Provision of various services (including backend APIs, MariaDB clusters, NATs, Redis)
- Design and operation of Kubernetes clusters (3 locations)
- Management of automated pipelines via Bitbucket Pipelines (continuous integration)
- IT administration for 6 individuals (SysOps)

156
src/pages/index.astro Normal file
View File

@@ -0,0 +1,156 @@
---
import { getCollection } from "astro:content";
import Layout from "@layouts/Layout.astro";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import LinkButton from "@components/LinkButton.astro";
import Hr from "@components/Hr.astro";
import Card from "@components/Card";
import Socials from "@components/Socials.astro";
import getSortedPosts from "@utils/getSortedPosts";
import slugify from "@utils/slugify";
import { SOCIALS } from "@config";
const posts = await getCollection("blog");
const sortedPosts = getSortedPosts(posts);
const featuredPosts = sortedPosts.filter(({ data }) => data.featured);
const socialCount = SOCIALS.filter(social => social.active).length;
---
<Layout>
<Header />
<main id="main-content">
<section id="hero">
<h1>Engineering Chaos</h1>
<a
target="_blank"
href="/rss.xml"
class="rss-link"
aria-label="rss feed"
title="RSS Feed"
>
<svg xmlns="http://www.w3.org/2000/svg" class="rss-icon"
><path
d="M19 20.001C19 11.729 12.271 5 4 5v2c7.168 0 13 5.832 13 13.001h2z"
></path><path
d="M12 20.001h2C14 14.486 9.514 10 4 10v2c4.411 0 8 3.589 8 8.001z"
></path><circle cx="6" cy="18" r="2"></circle>
</svg>
</a>
<p>
I'm Alex, a DevOps architect and software developer. I currently hold the role of DevOps Engineer at Materna, where I assist developers in accelerating web performance and provide guidance on various topics such as web development, Kubernetes, network security, and more.
</p>
<!-- <p>
Read the blog posts or check
<LinkButton
className="hover:text-skin-accent underline underline-offset-4 decoration-dashed"
href="https://github.com/satnaing/astro-paper#readme"
>
README
</LinkButton> for more info.
</p> -->
{
// only display if at least one social link is enabled
socialCount > 0 && (
<div class="social-wrapper">
<div class="social-links">Social Links:</div>
<Socials />
</div>
)
}
</section>
<Hr />
{
featuredPosts.length > 0 && (
<>
<section id="featured">
<h2>Featured</h2>
<ul>
{featuredPosts.map(({ data }) => (
<Card
href={`/posts/${slugify(data)}`}
frontmatter={data}
secHeading={false}
/>
))}
</ul>
</section>
<Hr />
</>
)
}
<section id="recent-posts">
<h2>Recent Posts</h2>
<ul>
{
sortedPosts.map(
({ data }, index) =>
index < 4 && (
<Card
href={`/posts/${slugify(data)}`}
frontmatter={data}
secHeading={false}
/>
)
)
}
</ul>
<div class="all-posts-btn-wrapper">
<LinkButton href="/posts">
All Posts
<svg xmlns="http://www.w3.org/2000/svg"
><path
d="m11.293 17.293 1.414 1.414L19.414 12l-6.707-6.707-1.414 1.414L15.586 11H6v2h9.586z"
></path>
</svg>
</LinkButton>
</div>
</section>
</main>
<Footer />
</Layout>
<style>
/* ===== Hero Section ===== */
#hero {
@apply pb-6 pt-8;
}
#hero h1 {
@apply my-4 inline-block text-3xl font-bold sm:my-8 sm:text-5xl;
}
#hero .rss-link {
@apply mb-6;
}
#hero .rss-icon {
@apply mb-2 h-6 w-6 scale-110 fill-skin-accent sm:mb-3 sm:scale-125;
}
#hero p {
@apply my-2;
}
.social-wrapper {
@apply mt-4 flex flex-col sm:flex-row sm:items-center;
}
.social-links {
@apply mb-1 mr-2 whitespace-nowrap sm:mb-0;
}
/* ===== Featured & Recent Posts Sections ===== */
#featured,
#recent-posts {
@apply pb-6 pt-12;
}
#featured h2,
#recent-posts h2 {
@apply text-2xl font-semibold tracking-wide;
}
.all-posts-btn-wrapper {
@apply my-8 text-center;
}
</style>

View File

@@ -0,0 +1,58 @@
---
import { CollectionEntry, getCollection } from "astro:content";
import Posts from "@layouts/Posts.astro";
import PostDetails from "@layouts/PostDetails.astro";
import getSortedPosts from "@utils/getSortedPosts";
import getPageNumbers from "@utils/getPageNumbers";
import slugify from "@utils/slugify";
import { SITE } from "@config";
export interface Props {
post: CollectionEntry<"blog">;
}
export async function getStaticPaths() {
const posts = await getCollection("blog", ({ data }) => !data.draft);
const postResult = posts.map(post => ({
params: { slug: slugify(post.data) },
props: { post },
}));
const pagePaths = getPageNumbers(posts.length).map(pageNum => ({
params: { slug: String(pageNum) },
}));
return [...postResult, ...pagePaths];
}
const { slug } = Astro.params;
const { post } = Astro.props;
const posts = await getCollection("blog");
const sortedPosts = getSortedPosts(posts);
const totalPages = getPageNumbers(sortedPosts.length);
const currentPage =
slug && !isNaN(Number(slug)) && totalPages.includes(Number(slug))
? Number(slug)
: 0;
const lastPost = currentPage * SITE.postPerPage;
const startPost = lastPost - SITE.postPerPage;
const paginatedPosts = sortedPosts.slice(startPost, lastPost);
---
{
post ? (
<PostDetails post={post} />
) : (
<Posts
posts={paginatedPosts}
pageNum={currentPage}
totalPages={totalPages.length}
/>
)
}

View File

@@ -0,0 +1,18 @@
---
import { SITE } from "@config";
import Posts from "@layouts/Posts.astro";
import getSortedPosts from "@utils/getSortedPosts";
import getPageNumbers from "@utils/getPageNumbers";
import { getCollection } from "astro:content";
const posts = await getCollection("blog");
const sortedPosts = getSortedPosts(posts);
const totalPages = getPageNumbers(sortedPosts.length);
const paginatedPosts = sortedPosts.slice(0, SITE.postPerPage);
---
<Posts posts={paginatedPosts} pageNum={1} totalPages={totalPages.length} />

21
src/pages/rss.xml.ts Normal file
View File

@@ -0,0 +1,21 @@
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
import getSortedPosts from "@utils/getSortedPosts";
import slugify from "@utils/slugify";
import { SITE } from "@config";
export async function get() {
const posts = await getCollection("blog");
const sortedPosts = getSortedPosts(posts);
return rss({
title: SITE.title,
description: SITE.desc,
site: SITE.website,
items: sortedPosts.map(({ data }) => ({
link: `posts/${slugify(data)}`,
title: data.title,
description: data.description,
pubDate: new Date(data.pubDatetime),
})),
});
}

27
src/pages/search.astro Normal file
View File

@@ -0,0 +1,27 @@
---
import { getCollection } from "astro:content";
import { SITE } from "@config";
import Layout from "@layouts/Layout.astro";
import Main from "@layouts/Main.astro";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import Search from "@components/Search";
// Retrieve all articles
const posts = await getCollection("blog", ({ data }) => !data.draft);
// List of items to search in
const searchList = posts.map(({ data }) => ({
title: data.title,
description: data.description,
data,
}));
---
<Layout title={`Search | ${SITE.title}`}>
<Header activeNav="search" />
<Main pageTitle="Search" pageDesc="Search any article ...">
<Search client:load searchList={searchList} />
</Main>
<Footer />
</Layout>

View File

@@ -0,0 +1,56 @@
---
import { CollectionEntry, getCollection } from "astro:content";
import Layout from "@layouts/Layout.astro";
import Main from "@layouts/Main.astro";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import Card from "@components/Card";
import getUniqueTags from "@utils/getUniqueTags";
import getPostsByTag from "@utils/getPostsByTag";
import slugify from "@utils/slugify";
import { SITE } from "@config";
import getSortedPosts from "@utils/getSortedPosts";
export interface Props {
post: CollectionEntry<"blog">;
tag: string;
}
export async function getStaticPaths() {
const posts = await getCollection("blog");
const tags = getUniqueTags(posts);
return tags.map(tag => {
return {
params: { tag },
props: { tag },
};
});
}
const { tag } = Astro.props;
const posts = await getCollection("blog", ({ data }) => !data.draft);
const tagPosts = getPostsByTag(posts, tag);
const sortTagsPost = getSortedPosts(tagPosts);
---
<Layout title={`Tag:${tag} | ${SITE.title}`}>
<Header activeNav="tags" />
<Main
pageTitle={`Tag:${tag}`}
pageDesc={`All the articles with the tag "${tag}".`}
>
<ul>
{
sortTagsPost.map(({ data }) => (
<Card href={`/posts/${slugify(data)}`} frontmatter={data} />
))
}
</ul>
</Main>
<Footer />
</Layout>

View File

@@ -0,0 +1,24 @@
---
import { getCollection } from "astro:content";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import Layout from "@layouts/Layout.astro";
import Main from "@layouts/Main.astro";
import Tag from "@components/Tag.astro";
import getUniqueTags from "@utils/getUniqueTags";
import { SITE } from "@config";
const posts = await getCollection("blog");
let tags = getUniqueTags(posts);
---
<Layout title={`Tags | ${SITE.title}`}>
<Header activeNav="tags" />
<Main pageTitle="Tags" pageDesc="All the tags used in posts.">
<ul>
{tags.map(tag => <Tag name={tag} size="lg" />)}
</ul>
</Main>
<Footer />
</Layout>