First Commit

This commit is contained in:
Ben Mosley
2025-04-14 11:31:32 -05:00
commit 155f90340a
19 changed files with 1334 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

320
app.py Normal file
View File

@@ -0,0 +1,320 @@
from flask import Flask, request, redirect, url_for, render_template, session, flash
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import func
from dotenv import load_dotenv
import os
from werkzeug.utils import secure_filename
from flask_login import LoginManager, login_required, logout_user, UserMixin, login_user
from flask_mail import Mail, Message
import json
from slugify import slugify
load_dotenv()
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///posts.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['UPLOAD_FOLDER'] = 'static/uploads'
app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY')
app.config['MAIL_SERVER'] = 'mail.bennyshouse.net'
app.config['MAIL_PORT'] = 587
app.config['MAIL_USE_TLS'] = True
app.config['MAIL_USERNAME'] = os.getenv("MAIL_USERNAME")
app.config['MAIL_PASSWORD'] = os.getenv("MAIL_PASSWORD")
app.config['MAIL_DEFAULT_SENDER'] = os.getenv("MAIL_DEFAULT_SENDER")
mail = Mail(app)
db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
USERNAME = os.getenv('FLASK_LOGIN_USER')
PASSWORD = os.getenv('FLASK_LOGIN_PASSWORD')
if not os.path.exists(app.config['UPLOAD_FOLDER']):
os.makedirs(app.config['UPLOAD_FOLDER'])
class User(UserMixin):
id = 1
@login_manager.user_loader
def load_user(user_id):
if user_id == "1":
return User()
return None
class BlogPost(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), nullable=False)
slug = db.Column(db.String(255), nullable=False)
content = db.Column(db.Text, nullable=False)
images = db.Column(db.Text, nullable=True)
category = db.Column(db.String(100), nullable=True)
tags = db.Column(db.Text, nullable=True)
pinned = db.Column(db.Boolean, default=False)
class ProjectPost(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(255), nullable=False)
slug = db.Column(db.String(255), nullable=False)
content = db.Column(db.Text, nullable=False)
images = db.Column(db.Text, nullable=True)
category = db.Column(db.String(100), nullable=True)
tags = db.Column(db.Text, nullable=True)
pinned = db.Column(db.Boolean, default=False)
with app.app_context():
db.create_all()
@app.template_filter('from_json')
def from_json(value):
try:
return json.loads(value)
except (TypeError, json.JSONDecodeError):
return []
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
username = request.form["username"]
password = request.form["password"]
if username == USERNAME and password == PASSWORD:
user = User()
login_user(user)
session.permanent = True
return redirect(url_for("admin_panel"))
flash("Invalid email or password!", "danger")
return render_template("login.html")
@app.route("/logout")
@login_required
def logout():
logout_user()
return redirect(url_for("index"))
@app.route('/admin')
@login_required
def admin_panel():
blogpost = BlogPost.query.order_by(BlogPost.id.desc()).all()
projectpost = ProjectPost.query.order_by(ProjectPost.id.desc()).all()
return render_template('admin.html', blogpost=blogpost, projectpost=projectpost)
@app.route('/newblog', methods=['GET', 'POST'])
@login_required
def new_blog():
if request.method == 'POST':
title = request.form['title']
content = request.form['content'].replace("\n", "<br>")
category = request.form['category']
tags = request.form['tags']
pinned = 'pinned' in request.form
images = request.files.getlist('images')
slug = slugify(title)
image_filenames = [secure_filename(image.filename) for image in images if image.filename]
for image, filename in zip(images, image_filenames):
image.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
new_post = BlogPost(
title=title,
slug=slug,
content=content,
category=category,
tags=tags,
pinned=pinned,
images=json.dumps(image_filenames)
)
db.session.add(new_post)
db.session.commit()
return redirect(url_for('blog'))
return render_template('new_blog.html')
@app.route('/newproject', methods=['GET', 'POST'])
@login_required
def new_project():
if request.method == 'POST':
title = request.form['title']
content = request.form['content'].replace("\n", "<br>")
category = request.form['category']
tags = request.form['tags']
pinned = 'pinned' in request.form
images = request.files.getlist('images')
slug = slugify(title)
image_filenames = [secure_filename(image.filename) for image in images if image.filename]
for image, filename in zip(images, image_filenames):
image.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
new_post = ProjectPost(
title=title,
slug=slug,
content=content,
category=category,
tags=tags,
pinned=pinned,
images=json.dumps(image_filenames)
)
db.session.add(new_post)
db.session.commit()
return redirect(url_for('projects'))
return render_template('new_project.html')
@app.route('/edit-blog/<slug>', methods=['GET', 'POST'])
@login_required
def edit_blog(slug):
blogpost = BlogPost.query.filter_by(slug=slug).first_or_404()
if request.method == 'POST':
blogpost.title = request.form['title']
blogpost.slug = slugify(blogpost.title)
blogpost.content = request.form['content'].replace("\n", "<br>")
blogpost.category = request.form['category']
blogpost.tags = request.form['tags']
blogpost.pinned = 'pinned' in request.form
images = request.files.getlist('images')
image_filenames = json.loads(blogpost.images) if blogpost.images else []
for image in images:
if image.filename:
filename = secure_filename(image.filename)
image.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
image_filenames.append(filename)
blogpost.images = json.dumps(image_filenames)
db.session.commit()
return redirect(url_for('view_blog', slug=blogpost.slug))
return render_template('edit_blog.html', blogpost=blogpost)
@app.route('/edit-project/<slug>', methods=['GET', 'POST'])
@login_required
def edit_project(slug):
projectpost = ProjectPost.query.filter_by(slug=slug).first_or_404()
if request.method == 'POST':
projectpost.title = request.form['title']
projectpost.slug = slugify(projectpost.title)
projectpost.content = request.form['content'].replace("\n", "<br>")
projectpost.category = request.form['category']
projectpost.tags = request.form['tags']
projectpost.pinned = 'pinned' in request.form
images = request.files.getlist('images')
image_filenames = json.loads(projectpost.images) if projectpost.images else []
for image in images:
if image.filename:
filename = secure_filename(image.filename)
image.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
image_filenames.append(filename)
projectpost.images = json.dumps(image_filenames)
db.session.commit()
return redirect(url_for('view_project', slug=projectpost.slug))
return render_template('edit_project.html', projectpost=projectpost)
@app.route('/delete-blog/<slug>', methods=['POST'])
@login_required
def delete_blog(slug):
blogpost = BlogPost.query.filter_by(slug=slug).first_or_404()
if blogpost.images:
for filename in json.loads(blogpost.images):
image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
if os.path.exists(image_path):
os.remove(image_path)
db.session.delete(blogpost)
db.session.commit()
return redirect(url_for('admin_panel'))
@app.route('/delete-project/<slug>', methods=['POST'])
@login_required
def delete_project(slug):
projectpost = ProjectPost.query.filter_by(slug=slug).first_or_404()
if projectpost.images:
for filename in json.loads(projectpost.images):
image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
if os.path.exists(image_path):
os.remove(image_path)
db.session.delete(projectpost)
db.session.commit()
return redirect(url_for('admin_panel'))
@app.route('/blogtag/<tag>')
def view_tag(tag):
tag_lower = tag.lower() # Normalize case
blogpost = BlogPost.query.filter(func.lower(BlogPost.tags).contains(tag_lower)).order_by(BlogPost.id.desc()).all()
return render_template('blog_tag_results.html', blogpost=blogpost, tag=tag)
@app.route('/projecttag/<tag>')
def view_project_tag(tag):
tag_lower = tag.lower() # Normalize case
projectpost = ProjectPost.query.filter(func.lower(ProjectPost.tags).contains(tag_lower)).order_by(ProjectPost.id.desc()).all()
return render_template('project_tag_results.html', projectpost=projectpost, tag=tag)
@app.route('/blog')
def blog():
blogpost = BlogPost.query.order_by(BlogPost.id.desc()).all()
return render_template('blog.html', blogpost=blogpost)
@app.route('/projects')
def projects():
projectpost = ProjectPost.query.order_by(ProjectPost.id.desc()).all()
return render_template('project.html', projectpost=projectpost)
@app.route('/blog/<slug>')
def view_blog(slug):
blogpost = BlogPost.query.filter_by(slug=slug).first_or_404()
return render_template('view_blog.html', blogpost=blogpost)
@app.route('/project/<slug>')
def view_project(slug):
projectpost = ProjectPost.query.filter_by(slug=slug).first_or_404()
return render_template('view_project.html', projectpost=projectpost)
@app.route("/contact", methods=["GET", "POST"])
def contact():
if request.method == "POST":
name = request.form.get("name")
email = request.form.get("email")
message = request.form.get("message")
# Compose the email
msg = Message(subject=f"New Contact Message from {name}",
sender=("Portfolio Updates", "ben@bennyshouse.net"),
recipients=["ben@bennyshouse.net"],
body=f"Name: {name}\nEmail: {email}\n\n{message}")
try:
mail.send(msg)
flash("Your message has been sent!", "success")
except Exception as e:
flash("Something went wrong. Please try again later.", "error")
print(f"Email error: {e}")
return redirect(url_for("contact"))
return render_template("contact.html")
@app.route('/resume')
def resume():
return render_template('resume.html')
@app.route('/about')
def about():
return render_template('about.html')
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run(debug=True)

52
templates/about.html Normal file
View File

@@ -0,0 +1,52 @@
{% extends "base.html" %}
{% block title %}About{% endblock %}
{% block content %}
<!-- Hero Section -->
<section class="text-center py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white">
<h1 class="text-5xl font-extrabold mb-4">About Me</h1>
<p class="text-xl mb-6">Learn more about who I am, what I do, and what drives me.</p>
</section>
<section class="py-16 bg-black text-white">
<div class="max-w-4xl mx-auto px-4 space-y-12">
<div>
<h2 class="text-3xl font-bold text-orange-600 mb-4">Who Am I?</h2>
<p class="text-lg leading-relaxed">
I'm a passionate developer and tech enthusiast focused on building useful, secure, and accessible digital experiences.
Whether its creating full-stack web applications, designing clean UIs, or managing network infrastructure — I love what I do.
</p>
</div>
<div>
<h2 class="text-3xl font-bold text-orange-600 mb-4">Skills & Tools</h2>
<ul class="grid sm:grid-cols-2 gap-4 text-lg list-disc pl-6">
<li>Python & Flask</li>
<li>HTML CSS and Javascript</li>
<li>SQL (MYSQL, SQLite)</li>
<li>Linux system administration</li>
<li>Networking & Security (WireGuard, Samba, DNS-Server)</li>
<li>Git & Deployment </li>
<li>API integrations</li>
</ul>
</div>
<!-- Philosophy or Interests -->
<div>
<h2 class="text-3xl font-bold text-orange-600 mb-4">What I'm About</h2>
<p class="text-lg leading-relaxed">
I believe technology should empower people — not confuse them. I enjoy creating tools that are intuitive, helpful, and secure.
I'm always learning, whether its diving into cybersecurity, mastering backend frameworks, or exploring automation and DevOps.
</p>
</div>
</div>
</section>
{% endblock %}

100
templates/admin.html Normal file
View File

@@ -0,0 +1,100 @@
{% extends "base.html" %}
{% block title %}Admin{% endblock %}
{% block content %}
<section class="py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white text-center">
<h1 class="text-5xl font-extrabold mb-4">Admin Dashboard</h1>
<p class="text-xl">Manage your blog posts and projects here.</p>
</section>
<div class="max-w-6xl mx-auto py-12 px-4 space-y-16">
<!-- Projects Section -->
<div>
<div class="flex justify-between items-center mb-6">
<h2 class="text-3xl font-bold text-orange-600">Project Options</h2>
<a href="{{ url_for('new_project') }}"
class="bg-orange-600 text-white px-4 py-2 rounded hover:bg-orange-700 transition shadow">+ New Project</a>
</div>
<div class="overflow-x-auto">
<table class="min-w-full table-auto border border-gray-300 bg-white rounded-lg shadow">
<thead class="bg-gray-100">
<tr class="text-left text-gray-700 uppercase text-sm">
<th class="px-4 py-3">Title</th>
<th class="px-4 py-3">Category</th>
<th class="px-4 py-3">Actions</th>
</tr>
</thead>
<tbody>
{% for projectpost in projectpost %}
<tr class="border-t border-gray-200">
<td class="text-black px-4 py-3 font-medium">{{ projectpost.title }}</td>
<td class="text-black px-4 py-3">{{ projectpost.category }}</td>
<td class="px-4 py-3 space-x-2">
<a href="{{ url_for('view_project', slug=projectpost.slug) }}"
class="inline-block bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600">View</a>
<a href="{{ url_for('edit_project', slug=projectpost.slug) }}"
class="inline-block bg-yellow-500 text-white px-3 py-1 rounded hover:bg-yellow-600">Edit</a>
<form action="{{ url_for('delete_project', slug=projectpost.slug) }}" method="POST" class="inline">
<button type="submit"
onclick="return confirm('Delete this Project?');"
class="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600">
Delete
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Blog Section -->
<div>
<div class="flex justify-between items-center mb-6">
<h2 class="text-3xl font-bold text-orange-600">Blog Options</h2>
<a href="{{ url_for('new_blog') }}"
class="bg-orange-600 text-white px-4 py-2 rounded hover:bg-orange-700 transition shadow">+ New Blog Post</a>
</div>
<div class="overflow-x-auto">
<table class="min-w-full table-auto border border-gray-300 bg-white rounded-lg shadow">
<thead class="bg-gray-100">
<tr class="text-left text-gray-700 uppercase text-sm">
<th class="px-4 py-3">Title</th>
<th class="px-4 py-3">Category</th>
<th class="px-4 py-3">Actions</th>
</tr>
</thead>
<tbody>
{% for blogpost in blogpost %}
<tr class="border-t border-gray-200">
<td class="text-black px-4 py-3 font-medium">{{ blogpost.title }}</td>
<td class="text-black px-4 py-3">{{ blogpost.category }}</td>
<td class="px-4 py-3 space-x-2">
<a href="{{ url_for('view_blog', slug=blogpost.slug) }}"
class="inline-block bg-blue-500 text-white px-3 py-1 rounded hover:bg-blue-600">View</a>
<a href="{{ url_for('edit_blog', slug=blogpost.slug) }}"
class="inline-block bg-yellow-500 text-white px-3 py-1 rounded hover:bg-yellow-600">Edit</a>
<form action="{{ url_for('delete_blog', slug=blogpost.slug) }}" method="POST" class="inline">
<button type="submit"
onclick="return confirm('Delete this post?');"
class="bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600">
Delete
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

85
templates/base.html Normal file
View File

@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" integrity="sha512-...snip..." crossorigin="anonymous" referrerpolicy="no-referrer" />
<title>{% block title %}Benjamin Mosley{% endblock %}</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
</head>
<body class="bg-black text-white font-sans min-h-screen flex flex-col">
<header class="bg-black text-white shadow" x-data="{ open: false }">
<div class="max-w-7xl mx-auto flex justify-between items-center p-6">
<h1>
<a href="/" class="text-xl font-bold">Benjamin Mosley</a>
</h1>
<!-- Hamburger button (shown on small screens) -->
<button @click="open = !open" class="sm:hidden focus:outline-none">
<svg x-show="!open" x-cloak xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 6h16M4 12h16M4 18h16" />
</svg>
<svg x-show="open" x-cloak xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<!-- Desktop nav -->
<nav class="hidden sm:flex space-x-4">
<a href="/blog" class="p-3 hover:text-blue-400">Blog</a>
<a href="/resume" class="p-3 hover:text-blue-400">Resume</a>
<a href="/projects" class="p-3 hover:text-blue-400">Projects</a>
<a href="/contact" class="p-3 hover:text-blue-400">Contact</a>
<a href="/about" class="p-3 hover:text-blue-400">About</a>
</nav>
</div>
<!-- Mobile nav -->
<div x-show="open" x-cloak class="sm:hidden bg-black border-t border-gray-800">
<nav class="flex flex-col p-4 space-y-2">
<a href="/blog" class="hover:text-blue-400">Blog</a>
<a href="/resume" class="hover:text-blue-400">Resume</a>
<a href="/projects" class="hover:text-blue-400">Projects</a>
<a href="/contact" class="hover:text-blue-400">Contact</a>
<a href="/about" class="hover:text-blue-400">About</a>
</nav>
</div>
</header>
<main class="flex-grow">
{% block content %}{% endblock %}
</main>
<footer class="bg-black text-white text-center py-6 border-t border-gray-800">
<div class="max-w-4xl mx-auto px-4">
<p class="text-sm">2025 Benjamin Mosley.</p>
<div class="flex justify-center gap-4 mt-2 text-sm">
<a href="https://github.com/Benny-ui-ux" target="_blank" class="hover:text-white transition">GitHub</a>
<a href="https://bennyshouse.net" class="hover:text-white transition">Business Site</a>
<a href="mailto:ben@bennyshouse.net" class="hover:text-white transition">Email</a>
</div>
</div>
</footer>
</body>
</html>

38
templates/blog.html Normal file
View File

@@ -0,0 +1,38 @@
{% extends "base.html" %}
{% block title %}Blog{% endblock %}
{% block content %}
<!-- Hero Section -->
<section class="text-center py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white">
<h1 class="text-5xl font-extrabold mb-4">Blog</h1>
<p class="text-xl mb-6">Insights, tutorials, and personal thoughts — straight from my mind to the page.</p>
</section>
<!-- Blog Posts Grid -->
<section id="posts" class="py-16 bg-black text-white">
<div class="max-w-6xl mx-auto px-4">
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{% for blogpost in blogpost %}
<div class="bg-white rounded-lg shadow p-6 hover:shadow-lg transition">
<h2 class="text-2xl font-bold text-red-800 mb-2">
<a href="{{ url_for('view_blog', slug=blogpost.slug) }}" class="hover:underline">{{ blogpost.title }}</a>
</h2>
<p class="text-sm text-orange-500 font-medium mb-4">{{ blogpost.category }}</p>
<div class="flex flex-wrap gap-2">
{% for tag in blogpost.tags.split(',') %}
<a href="{{ url_for('view_tag', tag=tag.strip()) }}"
class="inline-block bg-gray-800 text-white text-sm px-3 py-1 rounded-full hover:bg-gray-700 transition">
#{{ tag.strip() }}
</a>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,35 @@
{% extends "base.html" %}
{% block title %}Tag Results{% endblock %}
{% block content %}
<!-- Hero Section -->
<section class="text-center py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white">
<h1 class="text-5xl font-extrabold mb-4">Posts Tagged: <span class="text-white">#{{ tag }}</span></h1>
<p class="text-lg">Here are all blog posts with this tag.</p>
</section>
<!-- Tag Results Content -->
<section class="py-16 bg-black text-white">
<div class="max-w-5xl mx-auto px-4">
{% if blogpost %}
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{% for blogpost in blogpost %}
<div class="bg-white rounded-lg shadow p-6 hover:shadow-lg transition">
<h2 class="text-2xl font-bold text-red-800 mb-2">
<a href="{{ url_for('view_blog', slug=blogpost.slug) }}" class="hover:underline">
{{ blogpost.title }}
</a>
</h2>
<p class="text-sm text-orange-500 font-medium">{{ blogpost.category }}</p>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-center text-lg text-gray-600">No posts found with this tag.</p>
{% endif %}
</div>
</section>
{% endblock %}

58
templates/contact.html Normal file
View File

@@ -0,0 +1,58 @@
{% extends "base.html" %}
{% block title %}Contact{% endblock %}
{% block content %}
<section class="text-center py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white">
<h1 class="text-5xl font-extrabold mb-4">Contact Me</h1>
<p class="text-xl mb-6">Looking to connect? Here's where you can reach me!</p>
</section>
<section class="bg-black text-white py-16 px-6 md:px-20">
<div class="max-w-4xl mx-auto">
<div class="grid md:grid-cols-2 gap-12">
<!-- Contact Info -->
<div>
<h2 class="text-3xl font-bold mb-4 text-orange-600">Get in Touch</h2>
<p class="mb-4">Feel free to drop me a message via the form or through any of the channels below:</p>
<ul class="space-y-3 text-lg">
<li><strong>Email:</strong> <a href="mailto:you@example.com" class="text-blue-600 hover:underline">ben@bennyshouse.net</a></li>
<li><strong>Business Website:</strong> <a href="https://bennyshouse.net" class="text-blue-600 hover:underline">bennyshouse.net</a></li>
<li><strong>Location:</strong> Canyon, Texas</li>
</ul>
<div class="mt-6 flex space-x-4">
<a href="https://www.instagram.com/bennyshousefr/" class="text-orange-600 hover:text-orange-800 transition"><i class="fab fa-instagram fa-lg"></i></a>
<a href="https://www.linkedin.com/in/benjamin-mosley-849643329/" class="text-orange-600 hover:text-orange-800 transition"><i class="fab fa-linkedin fa-lg"></i></a>
<a href="https://github.com/Benny-ui-ux" class="text-orange-600 hover:text-orange-800 transition"><i class="fab fa-github fa-lg"></i></a>
</div>
</div>
<div>
<form method="POST" action="/contact" class="space-y-4">
<div>
<label for="name" class="block font-semibold mb-1">Name</label>
<input type="text" id="name" name="name" required class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
</div>
<div>
<label for="email" class="block font-semibold mb-1">Email</label>
<input type="email" id="email" name="email" required class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
</div>
<div>
<label for="message" class="block font-semibold mb-1">Message</label>
<textarea id="message" name="message" rows="5" required class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none"></textarea>
</div>
<div class="text-right">
<button type="submit" class="bg-orange-600 text-white px-6 py-2 rounded hover:bg-orange-700 transition">Send</button>
</div>
</form>
</div>
</div>
</div>
</section>
{% endblock %}

29
templates/edit_blog.html Normal file
View File

@@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block title %}Edit Blog{% endblock %}
{% block content %}
<form method="POST" enctype="multipart/form-data">
<label>Title:</label>
<input type="text" name="title" value="{{ blogpost.title }}" required>
<label>Content:</label>
<textarea name="content">{{ blogpost.content|replace('<br>', '\n') }}</textarea>
<label>Category:</label>
<input type="text" name="category" value="{{ blogpost.category }}">
<label>Tags:</label>
<input type="text" name="tags" value="{{ blogpost.tags }}">
<label>Upload New Images:</label>
<input type="file" name="images" multiple>
<button type="submit">Save Changes</button>
</form>
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends "base.html" %}
{% block title %}Edit Project{% endblock %}
{% block content %}
<form method="POST" enctype="multipart/form-data">
<label>Title:</label>
<input type="text" name="title" value="{{ projectpost.title }}" required>
<label>Content:</label>
<textarea name="content">{{ projectpost.content|replace('<br>', '\n') }}</textarea>
<label>Category:</label>
<input type="text" name="category" value="{{ projectpost.category }}">
<label>Tags:</label>
<input type="text" name="tags" value="{{ projectpost.tags }}">
<label>Upload New Images:</label>
<input type="file" name="images" multiple>
<button type="submit">Save Changes</button>
</form>
{% endblock %}

46
templates/index.html Normal file
View File

@@ -0,0 +1,46 @@
{% extends "base.html" %}
{% block title %}Benjamin Mosley{% endblock %}
{% block content %}
<section class="text-center py-16 bg-gradient-to-br from-red-800 to-orange-400 text-white">
<h1 class="mb-3 text-5xl font-extrabold mb-4">Benjamin Mosley's Personal Portfolio</h1>
<img src="{{ url_for('static', filename='Ben.png') }}" alt="Benjamin Mosley"
class="h-32 w-32 mx-auto shadow-lg border-4 border-white mb-6">
<p class="text-xlmb-6">I am Benjamin Mosley, a Computer Information Systems student with
a passion for building, creating, and learning.</p>
</section>
<section class="grid grid-cols-1 md:grid-cols-3 gap-6 p-10 text-white">
<div class="bg-gradient-to-br from-red-600 to-orange-300 p-6 rounded shadow text-center">
<h3 class="text-lg font-bold mb-2">Contact Me</h3>
<p>Have any questions? Reach out to me here</p>
<br>
<a href="/contact" class="bg-blue-400 inline-block text-white font-semibold px-4 py-2 rounded shadpw hpver:bg-gray-100 transition">Learn More</a>
</div>
<div class="bg-gradient-to-br from-red-600 to-orange-300 p-6 rounded shadow text-center">
<h3 class="text-lg font-bold mb-2">My Projects</h3>
<p>Check out my latest projects!</p>
<br>
<a href="/projects" class="bg-blue-400 inline-block text-white font-semibold px-4 py-2 rounded shadpw hpver:bg-gray-100 transition">Learn More</a>
</div>
<div class="bg-gradient-to-br from-red-600 to-orange-300 p-6 rounded shadow text-center">
<h3 class="text-lg font-bold mb-2">My Blog</h3>
<p>Read my latest Blog-Posts!</p>
<br>
<a href="/blog" class="bg-blue-400 inline-block text-white font-semibold px-4 py-2 rounded shadpw hpver:bg-gray-100 transition">Learn More</a>
</div>
</section>
{% endblock %}

66
templates/login.html Normal file
View File

@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<title>Login</title>
<style>
body {
background: linear-gradient(to right, #667eea, #764ba2);
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.login-container {
background: white;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
text-align: center;
max-width: 400px;
width: 100%;
}
.logo-placeholder {
width: 80px;
height: 80px;
background: #ddd;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
margin: 0 auto 1rem;
font-weight: bold;
color: #666;
}
.logo {
width: 100px; /* Adjust size as needed */
height: auto;
display: block;
margin: 0 auto 1rem; /* Centers the logo */
}
</style>
</head>
<body>
<div class="login-container">
<img src="{{ url_for('static', filename='Netdeploy.jpg') }}" alt="Logo" class="logo">
<h2 class="mb-3">Login</h2>
<form action="/login" method="POST">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary btn-block">Login</button>
</form>
</div>
</body>
</html>

78
templates/new_blog.html Normal file
View File

@@ -0,0 +1,78 @@
{% extends "base.html" %}
{% block title %}New Blog Post{% endblock %}
{% block content %}
<section class="py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white text-center">
<h1 class="text-5xl font-extrabold mb-2">Create a New Blog Post</h1>
<p class="text-lg">Share your thoughts, tutorials, or experiences with the world.</p>
</section>
<section class="py-16 bg-black text-white">
<div class="max-w-3xl mx-auto px-6">
<form action="{{ url_for('new_blog') }}" method="post" enctype="multipart/form-data" class="space-y-6">
<div>
<label for="title" class="block font-semibold mb-1">Post Title</label>
<input type="text" id="title" name="title" required
class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
</div>
<div>
<label for="content" class="block font-semibold mb-1">Post Content</label>
<textarea id="content" name="content" required rows="6"
class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none resize-none"></textarea>
</div>
<div>
<label for="category" class="block font-semibold mb-1">Category</label>
<input type="text" id="category" name="category" placeholder="What's This Post About?"
class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
</div>
<div>
<label for="tags" class="block font-semibold mb-1">Tags (comma-separated)</label>
<input type="text" id="tags" name="tags" placeholder="e.g., Flask, SQLite, API"
class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
</div>
<div>
<label for="images" class="block font-semibold mb-1">Upload Images</label>
<input type="file" name="images" id="images" multiple
class="text-black w-full border border-gray-300 rounded px-3 py-2 text-gray-700 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:bg-orange-600 file:text-white hover:file:bg-orange-700">
</div>
<div class="pt-4">
<button type="submit"
class="bg-orange-600 text-white px-6 py-2 rounded hover:bg-orange-700 transition">
Submit Post
</button>
</div>
</form>
<div class="mt-8 text-center">
<a href="{{ url_for('blog') }}" class="text-blue-600 hover:underline">← Back to Blog</a>
</div>
</div>
</section>
<!-- Textarea autoresize -->
<script>
document.addEventListener('DOMContentLoaded', function () {
const textarea = document.getElementById('content');
textarea.addEventListener('input', function () {
this.style.height = 'auto';
this.style.height = this.scrollHeight + 'px';
});
});
</script>
{% endblock %}

View File

@@ -0,0 +1,78 @@
{% extends "base.html" %}
{% block title %}New Project Post{% endblock %}
{% block content %}
<section class="py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white text-center">
<h1 class="text-5xl font-extrabold mb-2">Create a New Project Post</h1>
<p class="text-lg">Share your projects with the world.</p>
</section>
<section class="py-16 bg-black text-white">
<div class="max-w-3xl mx-auto px-6">
<form action="{{ url_for('new_project') }}" method="post" enctype="multipart/form-data" class="space-y-6">
<div>
<label for="title" class="block font-semibold mb-1">Post Title</label>
<input type="text" id="title" name="title" required
class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
</div>
<div>
<label for="content" class="block font-semibold mb-1">Post Content</label>
<textarea id="content" name="content" required rows="6"
class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none resize-none"></textarea>
</div>
<div>
<label for="category" class="block font-semibold mb-1">Category</label>
<input type="text" id="category" name="category" placeholder="What's This Post About?"
class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
</div>
<div>
<label for="tags" class="block font-semibold mb-1">Tags (comma-separated)</label>
<input type="text" id="tags" name="tags" placeholder="e.g., Flask, SQLite, API"
class="text-black w-full border border-gray-300 rounded px-4 py-2 focus:ring-2 focus:ring-orange-400 focus:outline-none">
</div>
<div>
<label for="images" class="block font-semibold mb-1">Upload Images</label>
<input type="file" name="images" id="images" multiple
class="text-black w-full border border-gray-300 rounded px-3 py-2 text-gray-700 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:bg-orange-600 file:text-white hover:file:bg-orange-700">
</div>
<div class="pt-4">
<button type="submit"
class="bg-orange-600 text-white px-6 py-2 rounded hover:bg-orange-700 transition">
Submit Post
</button>
</div>
</form>
<div class="mt-8 text-center">
<a href="{{ url_for('blog') }}" class="text-blue-600 hover:underline">← Back to Blog</a>
</div>
</div>
</section>
<!-- Textarea autoresize -->
<script>
document.addEventListener('DOMContentLoaded', function () {
const textarea = document.getElementById('content');
textarea.addEventListener('input', function () {
this.style.height = 'auto';
this.style.height = this.scrollHeight + 'px';
});
});
</script>
{% endblock %}

38
templates/project.html Normal file
View File

@@ -0,0 +1,38 @@
{% extends "base.html" %}
{% block title %}Projects{% endblock %}
{% block content %}
<!-- Hero Section -->
<section class="text-center py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white">
<h1 class="text-5xl font-extrabold mb-4">My Projects</h1>
<p class="text-xl mb-6">Heres a showcase of the things Ive built or contributed to.</p>
</section>
<!-- Projects Grid -->
<section id="projects" class="py-16 bg-black text-white">
<div class="max-w-6xl mx-auto px-4">
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{% for projectpost in projectpost %}
<div class="bg-white rounded-lg shadow p-6 hover:shadow-lg transition">
<h2 class="text-2xl font-bold text-red-800 mb-2">
<a href="{{ url_for('view_project', slug=projectpost.slug) }}" class="hover:underline">{{ projectpost.title }}</a>
</h2>
<p class="text-sm text-orange-500 font-medium mb-4">{{ projectpost.category }}</p>
<div class="flex flex-wrap gap-2">
{% for tag in projectpost.tags.split(',') %}
<a href="{{ url_for('view_project_tag', tag=tag.strip()) }}"
class="inline-block bg-orange-200 text-orange-900 text-sm px-3 py-1 rounded-full hover:bg-orange-300 transition">
#{{ tag.strip() }}
</a>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,35 @@
{% extends "base.html" %}
{% block title %}Tag Results{% endblock %}
{% block content %}
<!-- Hero Section -->
<section class="text-center py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white">
<h1 class="text-5xl font-extrabold mb-4">Posts Tagged: <span class="text-white">#{{ tag }}</span></h1>
<p class="text-lg">Here are all Project Posts with this tag.</p>
</section>
<!-- Tag Results Content -->
<section class="py-16 bg-black text-white">
<div class="max-w-5xl mx-auto px-4">
{% if projectpost %}
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{% for projectpost in projectpost %}
<div class="bg-white rounded-lg shadow p-6 hover:shadow-lg transition">
<h2 class="text-2xl font-bold text-red-800 mb-2">
<a href="{{ url_for('view_project', slug=projectpost.slug) }}" class="hover:underline">
{{ projectpost.title }}
</a>
</h2>
<p class="text-sm text-orange-500 font-medium">{{ projectpost.category }}</p>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-center text-lg text-gray-600">No posts found with this tag.</p>
{% endif %}
</div>
</section>
{% endblock %}

95
templates/resume.html Normal file
View File

@@ -0,0 +1,95 @@
{% extends "base.html" %}
{% block title %}About Me{% endblock %}
{% block content %}
<section class="text-center py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white">
<h1 class="text-5xl font-extrabold mb-4">Benjamin Mosley</h1>
<p class="text-xl">Aspiring IT Professional & Web Developer</p>
</section>
<section class="py-16 bg-black text-white">
<div class="max-w-4xl mx-auto px-6 space-y-12">
<div class="text-center space-y-2">
<p><strong>Email:</strong> <a href="mailto:ben@bennyshouse.net" class="text-blue-600 hover:underline">ben@bennyshouse.net</a></p>
<p><strong>Portfolio:</strong> <a href="https://benjaminmosley.com" class="text-blue-600 hover:underline">benjaminmosley.com</a></p>
<p><strong>Business Website:</strong> <a href="https://bennyshouse.net" class="text-blue-600 hover:underline">bennyshouse.net</a></p>
<p><strong>LinkedIn:</strong> <a href="https://www.linkedin.com/in/benjamin-mosley-849643329/" class="text-blue-600 hover:underline">Benjamin Mosley</a></p>
<p><strong>GitHub:</strong> <a href="https://github.com/Benny-ui-ux" class="text-blue-600 hover:underline">github.com/Benny-ui-ux</a></p>
</div>
<div>
<h2 class="text-3xl font-bold text-orange-600 mb-4">Education</h2>
<ul class="space-y-3 text-lg">
<li><strong>High School Graduation:</strong> Mother of Divine Grace June 2022</li>
<li><strong>College:</strong> West Texas A&M University<br>
<span class="text-sm text-white italic">Currently pursuing B.S. in Computer Information Systems, graduating Spring 2026</span>
</li>
</ul>
</div>
<div>
<h2 class="text-3xl font-bold text-orange-600 mb-4">Background</h2>
<p class="text-lg leading-relaxed">
Ive been working with computers for as long as I can remember — what started as curiosity has grown into a passion. My technical journey continues to evolve through real-world experience and ongoing education.
</p>
</div>
<div>
<h2 class="text-3xl font-bold text-orange-600 mb-4">Skills</h2>
<ul class="grid grid-cols-1 sm:grid-cols-2 gap-4 text-lg list-disc pl-6">
<li>Python Programming</li>
<li>Linux Operating Systems</li>
<li>Web Design & Hosting</li>
<li>SQL & Database Management</li>
<li>Leadership & Teamwork</li>
</ul>
</div>
<div>
<h2 class="text-3xl font-bold text-orange-600 mb-4">Work Experience</h2>
<div class="space-y-6 text-lg">
<div>
<h3 class="text-xl font-semibold">United Supermarkets Borger, TX</h3>
<p class="italic text-white">Grocery Team Lead | Nov 2019 Aug 2022</p>
<ul class="list-disc pl-5 mt-2">
<li>Stocked shelves and maintained floor organization</li>
<li>Assisted customers and team members</li>
<li>Managed ordering and inventory processes</li>
</ul>
</div>
<div>
<h3 class="text-xl font-semibold">The Pergola Shop Canyon, TX</h3>
<p class="italic text-white">Wood Stainer / Cutter | Aug 2022 Aug 2023</p>
<ul class="list-disc pl-5 mt-2">
<li>Prepped and treated wood materials for construction</li>
</ul>
</div>
<div>
<h3 class="text-xl font-semibold">United Supermarkets Canyon, TX</h3>
<p class="italic text-white">Service Center / Bookkeeper | Aug 2023 Present</p>
<ul class="list-disc pl-5 mt-2">
<li>Nightly accounting duties and managment of my team</li>
<li>Provided high-quality customer service</li>
</ul>
</div>
</div>
</div>
</div>
</section>
{% endblock %}

78
templates/view_blog.html Normal file
View File

@@ -0,0 +1,78 @@
{% extends "base.html" %}
{% block title %}{{ blogpost.title }}{% endblock %}
{% block content %}
<!-- Hero Title Section -->
<section class="py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white text-center">
<h1 class="text-5xl font-extrabold mb-4">{{ blogpost.title }}</h1>
<p class="text-lg italic">Category: {{ blogpost.category }}</p>
<div class="mt-4 flex justify-center flex-wrap gap-2">
{% for tag in blogpost.tags.split(',') %}
<a href="{{ url_for('view_tag', tag=tag.strip()) }}"
class="bg-white text-gray-800 text-sm px-3 py-1 rounded-full shadow hover:bg-gray-100 transition">
#{{ tag.strip() }}
</a>
{% endfor %}
</div>
</section>
<!-- Content Section -->
<section class="py-16 bg-black text-white">
<div class="max-w-4xl mx-auto px-4 space-y-8">
{% if blogpost.images %}
<div class="mb-5">
<h3 class="fs-3 fw-semibold text-orange mb-4">Gallery</h3>
<div id="blogImageCarousel" class="carousel slide shadow rounded overflow-hidden" data-bs-ride="carousel">
<div class="carousel-inner">
{% for image in blogpost.images|from_json %}
<div class="carousel-item {% if loop.first %}active{% endif %}">
<img src="{{ url_for('static', filename='uploads/' + image) }}" class="d-block w-100" alt="Post Image"
style="max-height: 500px; object-fit: cover;">
</div>
{% endfor %}
</div>
<!-- Controls -->
<button class="carousel-control-prev" type="button" data-bs-target="#blogImageCarousel" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#blogImageCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
<!-- Optional indicators -->
<div class="carousel-indicators">
{% for image in blogpost.images|from_json %}
<button type="button" data-bs-target="#blogImageCarousel" data-bs-slide-to="{{ loop.index0 }}"
{% if loop.first %}class="active" aria-current="true"{% endif %} aria-label="Slide {{ loop.index }}"></button>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<!-- Blog Content -->
<div class="prose lg:prose-lg max-w-none">
{{ blogpost.content | safe }}
</div>
<!-- Back Button -->
<div class="pt-10 text-center">
<a href="{{ url_for('blog') }}"
class="inline-block bg-orange-600 text-white px-6 py-2 rounded hover:bg-orange-700 transition">
← Back to Blog
</a>
</div>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,77 @@
{% extends "base.html" %}
{% block title %}{{ projectpost.title }}{% endblock %}
{% block content %}
<!-- Hero Title Section -->
<section class="py-20 bg-gradient-to-br from-red-800 to-orange-400 text-white text-center">
<h1 class="text-5xl font-extrabold mb-4">{{ projectpost.title }}</h1>
<p class="text-lg italic">Category: {{ projectpost.category }}</p>
<div class="mt-4 flex justify-center flex-wrap gap-2">
{% for tag in projectpost.tags.split(',') %}
<a href="{{ url_for('view_tag', tag=tag.strip()) }}"
class="bg-white text-gray-800 text-sm px-3 py-1 rounded-full shadow hover:bg-gray-100 transition">
#{{ tag.strip() }}
</a>
{% endfor %}
</div>
</section>
<!-- Content Section -->
<section class="py-16 bg-black text-white">
<div class="max-w-4xl mx-auto px-4 space-y-8">
{% if projectpost.images %}
<div class="mb-5">
<h3 class="fs-3 fw-semibold text-orange mb-4">Gallery</h3>
<div id="blogImageCarousel" class="carousel slide shadow rounded overflow-hidden" data-bs-ride="carousel">
<div class="carousel-inner">
{% for image in projectpost.images|from_json %}
<div class="carousel-item {% if loop.first %}active{% endif %}">
<img src="{{ url_for('static', filename='uploads/' + image) }}" class="d-block w-100" alt="Post Image"
style="max-height: 500px; object-fit: cover;">
</div>
{% endfor %}
</div>
<!-- Controls -->
<button class="carousel-control-prev" type="button" data-bs-target="#blogImageCarousel" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#blogImageCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
<!-- Optional indicators -->
<div class="carousel-indicators">
{% for image in projectpost.images|from_json %}
<button type="button" data-bs-target="#blogImageCarousel" data-bs-slide-to="{{ loop.index0 }}"
{% if loop.first %}class="active" aria-current="true"{% endif %} aria-label="Slide {{ loop.index }}"></button>
{% endfor %}
</div>
</div>
</div>
{% endif %}
<!-- Blog Content -->
<div class="prose lg:prose-lg max-w-none">
{{ projectpost.content | safe }}
</div>
<!-- Back Button -->
<div class="pt-10 text-center">
<a href="{{ url_for('projects') }}"
class="inline-block bg-orange-600 text-white px-6 py-2 rounded hover:bg-orange-700 transition">
← Back to Projects
</a>
</div>
</div>
</section>
{% endblock %}