mirror of
https://github.com/alexohneander/alexohneander-astro.git
synced 2025-08-24 15:01:32 +00:00
feat: initial commit
This commit is contained in:
42
src/pages/404.astro
Normal file
42
src/pages/404.astro
Normal 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>
|
18
src/pages/[ogTitle].svg.ts
Normal file
18
src/pages/[ogTitle].svg.ts
Normal 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
65
src/pages/experience.md
Normal 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
156
src/pages/index.astro
Normal 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>
|
58
src/pages/posts/[slug].astro
Normal file
58
src/pages/posts/[slug].astro
Normal 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}
|
||||
/>
|
||||
)
|
||||
}
|
18
src/pages/posts/index.astro
Normal file
18
src/pages/posts/index.astro
Normal 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
21
src/pages/rss.xml.ts
Normal 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
27
src/pages/search.astro
Normal 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>
|
56
src/pages/tags/[tag].astro
Normal file
56
src/pages/tags/[tag].astro
Normal 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>
|
24
src/pages/tags/index.astro
Normal file
24
src/pages/tags/index.astro
Normal 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>
|
Reference in New Issue
Block a user