320 lines
11 KiB
Python
320 lines
11 KiB
Python
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) |