first commit

This commit is contained in:
Raven Scott 2024-09-16 07:53:26 -04:00
commit 4df0d17183
10 changed files with 954 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules
package-lock.json
.env

80
README.md Normal file
View File

@ -0,0 +1,80 @@
Here is a `README.md` file for your project:
```markdown
# Raven Scott Blog Website
This repository contains the code for Raven Scott's blog website, where markdown files serve as blog posts, and visitors can browse through posts, learn more about Raven, and contact him directly.
## Project Structure
```bash
raven-scott-website
├── app.js # Main server-side logic using Express.js
├── markdown # Folder containing markdown files for blog posts
│ └── API Caching for Minecraft Servers.md
├── package-lock.json # Auto-generated package lock file
├── package.json # Dependencies and project metadata
├── public # Static files such as CSS, images
│ └── css
│ └── styles.css # Custom styles for the website
└── views # EJS template files for rendering HTML
├── about.ejs # 'About Me' page template
├── blog-post.ejs # Blog post page template
├── contact.ejs # Contact form page template
└── index.ejs # Homepage displaying recent blog posts
```
## Features
- **Markdown Blog Posts**: Blog posts are written in Markdown, located in the `markdown` folder. The content is rendered with [marked](https://www.npmjs.com/package/marked) and supports code syntax highlighting using [highlight.js](https://highlightjs.org/).
- **Dynamic Blog Post Display**: Each markdown file is dynamically converted into a blog post with a readable URL slug. The homepage shows the latest posts, and each post has its own dedicated page.
- **Contact Form**: Visitors can use the contact form to send inquiries directly. Messages are handled via `nodemailer`.
- **Responsive Design**: The website uses [Bootstrap](https://getbootstrap.com/) to ensure a clean and responsive layout across devices.
- **Professional Look and Feel**: The custom CSS in `styles.css` provides a dark theme with accent colors for a professional, modern look.
## Installation
1. Clone the repository:
```bash
git clone git@git.ssh.surf:snxraven/ravenscott-blog.git
cd raven-scott-website
```
2. Install the dependencies:
```bash
npm install
```
3. Create a `.env` file to set up environment variables (e.g., for the email service using `nodemailer`):
```bash
touch .env
```
Add the following:
```
PORT=3000
```
4. Run the project:
```bash
npm start
```
The server will run on [http://localhost:3000](http://localhost:3000).
## Project Details
- **Template Engine**: [EJS](https://ejs.co/) is used to render dynamic HTML views.
- **Markdown Parsing**: Blog posts are stored in the `markdown` folder, and the app converts them to HTML using `marked`.
- **Code Highlighting**: Any code snippets in the markdown files are automatically highlighted with `highlight.js`.
- **Routing**:
- `/`: Displays the homepage with the latest blog posts.
- `/about`: About Me page where Raven shares his passions and interests.
- `/contact`: Contact page where visitors can submit inquiries.
- `/blog/:slug`: Dynamically generated blog post pages based on the markdown file.

124
app.js Normal file
View File

@ -0,0 +1,124 @@
require('dotenv').config(); // Load environment variables
const express = require('express');
const path = require('path');
const fs = require('fs');
const { marked } = require('marked');
const nodemailer = require('nodemailer');
const hljs = require('highlight.js');
const app = express();
// Set options for marked to use highlight.js for syntax highlighting
marked.setOptions({
highlight: function (code, language) {
// Check if the language is valid
const validLanguage = hljs.getLanguage(language) ? language : 'plaintext';
return hljs.highlight(validLanguage, code).value;
}
});
// Set EJS as templating engine
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
// Middleware to parse URL-encoded bodies (form submissions)
app.use(express.urlencoded({ extended: false }));
// Serve static files (CSS, Images)
app.use(express.static(path.join(__dirname, 'public')));
// Function to load and parse markdown files and extract lead
function loadMarkdownWithLead(file) {
const markdownContent = fs.readFileSync(path.join(__dirname, 'markdown', file), 'utf-8');
let lead = '';
let contentMarkdown = markdownContent;
// Detect and extract the lead section
const leadKeyword = '<!-- lead -->';
if (contentMarkdown.includes(leadKeyword)) {
const [beforeLead, afterLead] = contentMarkdown.split(leadKeyword);
// Extract the first paragraph after the lead keyword
lead = afterLead.split('\n').find(line => line.trim() !== '').trim();
// Remove the lead from the main content
contentMarkdown = beforeLead + afterLead.replace(lead, '').trim();
}
// Convert markdown to HTML
const contentHtml = marked.parse(contentMarkdown);
return { contentHtml, lead };
}
// Function to convert a title (with spaces) into a URL-friendly slug (with dashes)
function titleToSlug(title) {
return title.replace(/\s+/g, '-').toLowerCase(); // Always lowercase the slug
}
// Function to convert a slug (with dashes) back into a readable title (with spaces)
function slugToTitle(slug) {
return slug.replace(/-/g, ' ');
}
// Function to load all blog posts
function getAllBlogPosts() {
const blogFiles = fs.readdirSync(path.join(__dirname, 'markdown')).filter(file => file.endsWith('.md'));
return blogFiles.map(file => {
const title = file.replace('.md', '').replace(/-/g, ' '); // Keep original casing for title
const slug = titleToSlug(title); // Convert title to slug (lowercase)
return {
title, // Original casing title
slug
};
});
}
// Home Route (Blog Home)
app.get('/', (req, res) => {
const blogPosts = getAllBlogPosts();
res.render('index', { title: 'Raven Scott Blog', blogPosts });
});
// About Route
app.get('/about', (req, res) => {
res.render('about', { title: 'About Raven Scott' });
});
// Display the Request a Quote form
app.get('/contact', (req, res) => {
res.render('contact', { title: 'Contact Raven Scott', msg: undefined });
});
// Blog Post Route
app.get('/blog/:slug', (req, res) => {
const slug = req.params.slug;
const markdownFile = fs.readdirSync(path.join(__dirname, 'markdown'))
.find(file => titleToSlug(file.replace('.md', '')) === slug);
if (markdownFile) {
const originalTitle = markdownFile.replace('.md', ''); // Original title with casing
const blogPosts = getAllBlogPosts();
const { contentHtml, lead } = loadMarkdownWithLead(markdownFile);
res.render('blog-post', {
title: originalTitle, // Use the original title with casing
content: contentHtml,
lead: lead,
blogPosts
});
} else {
res.status(404).render('404', { title: 'Post not found' });
}
});
// Request a Quote form remains unchanged
// ================================
// Server Listening
// ================================
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});

View File

@ -0,0 +1,185 @@
<!-- lead -->
Finding an efficient way to cache data into memory.
# Building a Fast and Efficient Minecraft Server Cache Manager in Node.js
In this article, we will take an in-depth look at how to build a cache manager in Node.js to drastically improve the performance of your application, specifically for listing Minecraft servers. Without caching, the response time for listing all the servers was over 2 minutes due to multiple heavy computations and I/O operations. With the introduction of a caching mechanism, this time was reduced to an impressive 2 milliseconds. Lets dive into the code and understand how this was achieved.
## Overview of the System
The purpose of this system is to manage a list of Minecraft servers efficiently. The core functionality includes fetching server information, such as the server's MOTD (Message of the Day), online status, game version, and operational details (e.g., ops, whitelist, banned players). The data is sourced from Docker containers, local file systems, and remote authentication services.
### Problem Statement
When youre dealing with numerous Minecraft servers, querying real-time server data can become very expensive in terms of performance. Each query requires:
- Accessing container information via Docker.
- Checking server online status via network requests.
- Fetching MOTD and other server metadata.
- Interacting with the file system to fetch or generate tokens.
These operations, especially when scaled to multiple servers, can significantly slow down the response time. Without caching, the requests took over 2 minutes to process. This system mitigates those delays by introducing an efficient in-memory cache.
## The Caching Mechanism
The system employs a caching layer that stores various pieces of server information (MOTD, online status, etc.) in memory. This avoids repeated heavy I/O and network operations, thus drastically reducing the request time.
### Key Components of the Cache
1. **MOTD Cache:** Stores the Message of the Day for each server.
2. **Online Status Cache:** Stores whether a server is online or offline.
3. **Token Cache:** Stores authentication tokens for server owners.
4. **Container Info Cache:** Caches Docker container details for each server.
5. **Ops, Whitelist, Banned Players Caches:** Caches server-specific administrative data.
These caches are refreshed periodically and stored in an in-memory object, reducing the need for redundant calls to external systems.
## Code Walkthrough: The Cache Manager
### Directory and File Structure
- **Cache Directory:** The `cacheDir` (in this case, `/home/cache`) stores JSON files that provide metadata for each Minecraft server. Each file corresponds to a specific server and includes connection details and server-specific configuration data.
```javascript
const cacheDir = "/home/cache";
const cache = {
motd: {},
online: {},
token: {},
containerInfo: {},
ops: {},
whitelist: {},
whitelistEnable: {},
banned: {}
};
```
The `cache` object holds the in-memory data for all servers, avoiding the need to repeatedly fetch this data from external sources.
### Server Listing Function
Heres the code for listing all servers, including how caching is used to optimize performance:
```javascript
exports.list_all_servers = async function (req, res) {
let secret_key = req.params["secret_key"];
if (secret_key !== config.secret_key) {
return res.status(401).json({ success: false, error: "Unauthorized." });
}
try {
const files = await fs.promises.readdir(cacheDir);
let jumpnode_servers = [];
for (const file of files) {
const filePath = path.join(cacheDir, file);
const data = await jsonfile.readFile(filePath);
const serverName = path.basename(file, path.extname(file)).replace('.mc', '');
// Skip certain servers
if (serverName.includes(".link")) {
continue;
}
// Fetch data from cache or fallback to fetching from the source
const connectString = data.connect;
const motd = cache.motd[connectString]?.value ?? null;
const online = cache.online[connectString]?.value ?? false;
const token = cache.token[serverName]?.value ?? null;
const containerInfo = cache.containerInfo[serverName]?.value ?? null;
const ops = cache.ops[serverName]?.value ?? null;
const whitelist = cache.whitelist[serverName]?.value ?? null;
const whitelistEnable = cache.whitelistEnable[serverName]?.value ?? null;
const banned = cache.banned[serverName]?.value ?? null;
if (!containerInfo) {
continue; // Skip servers with no container info
}
// Server information is stored in jumpnode_servers array
const serverInfo = {
serverName,
gameVersion: containerInfo.Config.Image.split(':').pop(),
connect: data.connect,
ownersToken: token,
ops,
whitelist,
whitelistEnable,
banlist: banned,
motd,
online
};
jumpnode_servers.push(serverInfo);
}
return res.json({ success: true, servers: jumpnode_servers });
} catch (err) {
console.error("Error reading cache directory:", err);
return res.status(500).json({ success: false, error: err.message });
}
};
```
This function first checks the secret key for authentication. If the secret key is valid, it proceeds to list all servers. The server data is retrieved from the in-memory cache, avoiding the need to re-fetch the data from slow external sources like Docker, network connections, and the file system.
### Caching Core Functions
#### Fetching MOTD
The function `fetchMOTD` sends a custom status request packet to the server and waits for its response. The result is cached, so future requests can serve the MOTD immediately without querying the server again.
```javascript
async function fetchMOTD(connectString) {
const client = new net.Socket();
const serverPort = connectString.split(':')[1];
// Sends the packet to get the MOTD
client.connect(serverPort, 'my-mc.link', () => {
client.write(Buffer.from([0xFE, 0x01]));
});
// Process and cache the response
client.on('data', (data) => {
const response = data.toString('utf8').split('\x00\x00\x00');
if (response.length >= 6) {
const motd = response[3].replace(/\u0000/g, '');
cache.motd[connectString] = { value: motd, timestamp: Date.now() };
}
client.destroy();
});
}
```
#### Fetching Container Info
The `fetchContainerInfo` function interacts with Docker to fetch container details for each Minecraft server. Dockerode is used to communicate with the Docker API. The result is cached in memory to avoid repeatedly querying Docker for container data.
```javascript
async function fetchContainerInfo(serverName) {
const container = docker.getContainer(serverName);
const containerInfo = await container.inspect();
cache.containerInfo[serverName] = { value: containerInfo, timestamp: Date.now() };
}
```
#### Other Cache Functions
Similar functions exist for fetching online status, tokens, ops, whitelist, and banned player lists, all of which follow the same caching pattern.
## Cache Update Strategy
The cache is updated by periodically calling the `updateCache` function, which refreshes the cache every 60 seconds:
```javascript
setInterval(updateCache, 60000);
```
This ensures that the cache is kept relatively up-to-date while still providing the performance benefits of avoiding repeated heavy I/O operations. The cache holds both the value and a timestamp, allowing future improvements like time-based cache invalidation if needed.
## Conclusion
By introducing an in-memory caching system, this application was able to reduce request times from over 2 minutes to just 2 milliseconds. This system efficiently caches key server data, eliminating the need to repeatedly query external services like Docker, network services, and file systems. This approach is especially useful in applications with high I/O overhead and network latency, allowing for faster and more responsive interactions.
This caching strategy could be adapted and extended further, for instance by adding cache expiration policies or integrating Redis for distributed caching in a multi-node architecture.

22
package.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "geeks",
"version": "1.0.0",
"description": "",
"main": "README.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"body-parser": "^1.20.3",
"bootstrap": "^5.3.3",
"dotenv": "^16.4.5",
"ejs": "^3.1.10",
"express": "^4.21.0",
"highlight.js": "^11.10.0",
"marked": "^14.1.2",
"nodemailer": "^6.9.15"
}
}

139
public/css/styles.css Normal file
View File

@ -0,0 +1,139 @@
body {
font-family: 'Roboto', sans-serif;
display: flex;
flex-direction: column;
min-height: 100vh;
color: #333;
margin: 0;
}
main {
flex-grow: 1;
}
.bg-primary {
--bs-bg-opacity: 1;
background-color: rgb(0 0 0) !important;
}
.navbar {
background-color: #121212;
}
.navbar-brand {
font-size: 1.75rem;
font-weight: bold;
color: #ffffff;
}
.navbar-nav .nav-link {
font-size: 1.15rem;
padding-right: 1rem;
color: #ffffff;
}
header {
background: #000000;
color: #fff;
padding: 2px 0;
text-align: center;
height: auto;
}
h1 {
font-size: 3rem;
font-weight: bold;
}
p.lead {
font-size: 1.5rem;
}
.btn-primary {
font-size: 1.25rem;
padding: 10px 20px;
background-color: #000000;
border: none;
}
.btn-primary:hover {
background-color: #000000;
}
footer {
background-color: #121212;
color: #fff;
padding: 20px 0;
text-align: center;
margin-top: auto;
}
.footer-logo {
font-size: 1.5rem;
font-weight: bold;
}
.footer-links a {
color: #999;
text-decoration: none;
margin-right: 1rem;
}
.footer-links a:hover {
color: #fff;
}
/* Custom Styles for Navbar and Dropdown */
.navbar {
background-color: #121212;
}
.navbar-brand {
font-size: 1.75rem;
font-weight: bold;
color: #ffffff;
}
.navbar-nav .nav-link {
font-size: 1.15rem;
color: #ffffff;
padding-right: 1rem;
}
.navbar-nav .nav-link:hover {
color: #2c5364;
}
/* Custom Dropdown Styling */
.custom-dropdown {
background-color: #1e1e1e;
border: none;
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.3);
}
.custom-dropdown .dropdown-item {
color: #ffffff;
font-size: 1.1rem;
padding: 10px 20px;
background-color: #1e1e1e;
transition: background-color 0.3s ease, color 0.3s ease;
}
.custom-dropdown .dropdown-item:hover {
background-color: #000000;
color: #ffffff;
}
.custom-dropdown .dropdown-item:active {
background-color: #000000;
color: #ffffff;
}
/* Mobile Toggler */
.navbar-toggler {
border-color: #ffffff;
}
.navbar-toggler-icon {
color: #ffffff;
}

153
views/about.ejs Normal file
View File

@ -0,0 +1,153 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Meta and Title -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>About Me - Raven Scott</title>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css">
<!-- Font Awesome CSS for Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="/css/styles.css">
<style>
/* Custom styles for a more professional look */
body {
background-color: #1a1a1a;
color: #e0e0e0;
}
.navbar {
background-color: #212529;
}
.about-me h2,
.about-me h3 {
color: #ffffff;
font-weight: bold;
}
.about-me p {
font-size: 1.1rem;
line-height: 1.7;
color: #d1d1d1;
}
.about-me {
padding: 50px 0;
}
.container {
max-width: 900px;
margin: auto;
}
.footer-logo {
font-size: 1.5rem;
font-weight: bold;
}
.footer-links a {
color: #9a9a9a;
font-size: 0.9rem;
}
.footer-links a:hover {
color: #ffffff;
}
.btn-primary {
background-color: #007bff;
border-color: #007bff;
}
.btn-primary:hover {
background-color: #0056b3;
border-color: #004085;
}
/* Add padding for better text readability */
.about-me p {
padding-bottom: 15px;
}
/* Separator style for sections */
.section-divider {
width: 80px;
height: 3px;
background-color: #007bff;
margin: 20px 0;
border-radius: 2px;
}
</style>
</head>
<body>
<!-- Navigation Bar -->
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/">Raven Scott</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/about">About Me</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/contact">Contact</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- About Me Section -->
<section class="about-me py-5">
<div class="container text-center">
<h2 class="text-white mb-4">About Me</h2>
<div class="section-divider"></div>
<p class="lead">Hi, Im Raven Scott, a Linux enthusiast and problem solver with a deep passion for technology and creativity. I thrive in environments where I can learn, experiment, and turn ideas into reality. Whether it's building systems, coding, or tackling complex technical challenges, I find joy in using technology to make life easier and more efficient.</p>
<p>My passion for Linux and open-source technologies began early on, and since then, Ive been on a continuous journey of growth and discovery. From troubleshooting networking issues to optimizing servers for performance, I love diving deep into the intricate details of how things work. The thrill of solving problems, especially when it comes to system security or performance optimization, is what fuels me every day.</p>
<h3 class="text-white mt-5">What Drives Me</h3>
<div class="section-divider"></div>
<p>Im passionate about more than just the technical side. I believe in the power of technology to bring people together, and thats why Im dedicated to creating platforms and solutions that are accessible and impactful. Whether it's hosting services, developing peer-to-peer applications, or automating complex tasks, Im always exploring new ways to push the boundaries of what's possible.</p>
<p>Outside of work, I love contributing to community projects and sharing my knowledge with others. Helping people grow their own skills is one of the most rewarding aspects of what I do. From mentoring to writing documentation, Im constantly looking for ways to give back to the tech community.</p>
<h3 class="text-white mt-5">Creative Side</h3>
<div class="section-divider"></div>
<p>When Im not deep in the technical world, Im exploring my creative side through music. I run my own music label, where I produce and distribute AI-generated music across all platforms. Music and technology blend seamlessly for me, as both are outlets for innovation and expression.</p>
<p>In everything I do, from coding to creating music, my goal is to keep learning, growing, and sharing my passion with the world. If you ever want to connect, collaborate, or simply chat about tech or music, feel free to reach out!</p>
</div>
</section>
<!-- Footer -->
<footer class="bg-dark text-white text-center py-4">
<div class="container">
<h4 class="footer-logo mb-3">Never Stop Learning</h4>
<p class="footer-links mb-3">
<a href="/" class="text-white text-decoration-none me-3">Home</a>
<a href="/about" class="text-white text-decoration-none me-3">About</a>
<a href="/contact" class="text-white text-decoration-none">Contact</a>
</p>
<p class="mb-0">&copy; 2024 Raven Scott. All rights reserved.</p>
</div>
</footer>
<!-- Bootstrap JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

77
views/blog-post.ejs Normal file
View File

@ -0,0 +1,77 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
<!-- Stylesheets -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<link rel="stylesheet" href="/css/styles.css">
<!-- Highlight.js CSS for Syntax Highlighting -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/default.min.css">
</head>
<body>
<!-- Navbar -->
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/">Raven Scott</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link active" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/about">About Me</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/contact">Contact</a>
</li>
</ul>
</div>
</div>
</nav>
<header class="bg-primary text-white text-center py-5">
<h1><%= title %></h1>
<p class="lead"><%= lead %></p> <!-- Lead is dynamically set here -->
</header>
<main class="container my-5">
<!-- Render Markdown content as HTML -->
<div class="markdown-content">
<%- content %>
</div>
</main>
<!-- Footer -->
<footer class="bg-dark text-white text-center py-4">
<div class="container">
<h4 class="footer-logo mb-3">Never Stop Learning</h4>
<p class="footer-links mb-3">
<a href="/" class="text-white text-decoration-none me-3">Home</a>
<a href="/about" class="text-white text-decoration-none me-3">About</a>
<a href="/contact" class="text-white text-decoration-none">Contact</a>
</p>
<p class="mb-0">&copy; 2024 Raven Scott. All rights reserved.</p>
</div>
</footer>
<!-- Scripts -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<!-- Highlight.js Script -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js"></script>
<script>
// Initialize Highlight.js
document.addEventListener('DOMContentLoaded', (event) => {
document.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block);
});
});
</script>
</body>
</html>

96
views/contact.ejs Normal file
View File

@ -0,0 +1,96 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Meta and Title -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Contact Me - Raven Scott</title>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css">
<!-- Font Awesome CSS for Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="/css/styles.css">
</head>
<body class="bg-dark text-white">
<!-- Navigation Bar -->
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/">Raven Scott</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link active" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/about">About Me</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/contact">Contact</a>
</li>
</ul>
</div>
</div>
</nav>
<!-- Contact Me Section -->
<header class="d-flex align-items-center justify-content-center text-center py-5">
<div class="container">
<h2 class="mb-4 text-white">Contact Me</h2>
<p class="lead text-white">Have a question or need help with a project? Fill out the form below, and I'll be in touch!</p>
<!-- Display success or error message -->
<% if (typeof msg !== 'undefined') { %>
<div class="alert alert-info mt-3">
<%= msg %>
</div>
<% } %>
<!-- Contact Form -->
<form action="/contact" method="POST" class="mt-4">
<div class="mb-3">
<label for="name" class="form-label">Your Name<span class="text-danger">*</span></label>
<input type="text" class="form-control bg-dark text-white border-secondary" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="email" class="form-label">Email Address<span class="text-danger">*</span></label>
<input type="email" class="form-control bg-dark text-white border-secondary" id="email" name="email" required>
</div>
<div class="mb-3">
<label for="subject" class="form-label">Subject<span class="text-danger">*</span></label>
<input type="text" class="form-control bg-dark text-white border-secondary" id="subject" name="subject" required>
</div>
<div class="mb-3">
<label for="message" class="form-label">Your Message<span class="text-danger">*</span></label>
<textarea class="form-control bg-dark text-white border-secondary" id="message" name="message" rows="6" required></textarea>
</div>
<button type="submit" class="btn btn-primary">Send Message</button>
</form>
</div>
</header>
<!-- Footer -->
<footer class="bg-dark text-white text-center py-4">
<div class="container">
<h4 class="footer-logo mb-3">Never Stop Learning</h4>
<p class="footer-links mb-3">
<a href="/" class="text-white text-decoration-none me-3">Home</a>
<a href="/about" class="text-white text-decoration-none me-3">About</a>
<a href="/contact" class="text-white text-decoration-none">Contact</a>
</p>
<p class="mb-0">&copy; 2024 Raven Scott. All rights reserved.</p>
</div>
</footer>
<!-- Bootstrap JS Bundle -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

74
views/index.ejs Normal file
View File

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/styles.css">
</head>
<body>
<!-- Navbar -->
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/">raven-scott.fyi</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link active" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/about">About Me</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/contact">Contact</a>
</li>
</ul>
</div>
</div>
</nav>
<header class="py-5">
<div class="container text-center">
<h1>Welcome to my long form post blog</h1>
<p class="lead">Latest articles and insights from Raven Scott</p>
</div>
</header>
<section class="py-5">
<div class="container">
<h2>Recent Posts</h2>
<div class="row">
<% blogPosts.forEach(post => { %>
<div class="col-md-4">
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title"><%= post.title %></h5>
<a href="/blog/<%= post.slug %>" class="btn btn-primary">Read More</a>
</div>
</div>
</div>
<% }) %>
</div>
</div>
</section>
<footer class="bg-dark text-white text-center py-4">
<div class="container">
<h4 class="footer-logo mb-3">Never Stop Learning</h4>
<p class="footer-links mb-3">
<a href="/" class="text-white text-decoration-none me-3">Home</a>
<a href="/about" class="text-white text-decoration-none me-3">About</a>
<a href="/contact" class="text-white text-decoration-none">Contact</a>
</p>
<p class="mb-0">&copy; 2024 Raven Scott. All rights reserved.</p>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>