Major Changes: - Migrated web framework from Quart (async) to Flask (sync) - Added MariaDB database integration with SQLAlchemy ORM - Implemented dynamic content management for portfolio New Features: - Database models for Profile, Skills, Projects, ProjectTags, and SocialLinks - RESTful API endpoints for CRUD operations on all entities - Database initialization script (init_db.py) with sample data - Docker Compose configuration with MariaDB service Modified Files: - app.py: Replaced Quart with Flask, added database initialization - config.py: Added database configuration with environment variables - routes/home.py: Converted async to sync, added database queries - requirements.txt: Replaced Quart/Hypercorn with Flask/Gunicorn, added Flask-SQLAlchemy and PyMySQL - docker-compose.yml: Added MariaDB service with health checks - templates/: Updated all templates to use dynamic data from database with Jinja2 - .env.example: Added database configuration variables - README.md: Complete rewrite with new setup instructions and API documentation New Files: - models.py: SQLAlchemy models for all database entities - init_db.py: Database initialization script - routes/api.py: REST API endpoints for content management Benefits: - Simplified architecture (sync vs async) - Better ecosystem compatibility - Dynamic content management via database - Easy content updates through REST API - Improved deployment with standard WSGI server (Gunicorn)
134 lines
4.6 KiB
Python
134 lines
4.6 KiB
Python
"""
|
|
Database models for Portfolio Application
|
|
Uses SQLAlchemy ORM with MariaDB
|
|
"""
|
|
from flask_sqlalchemy import SQLAlchemy
|
|
from datetime import datetime
|
|
|
|
db = SQLAlchemy()
|
|
|
|
|
|
class Profile(db.Model):
|
|
"""Store personal profile information"""
|
|
__tablename__ = 'profile'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
title = db.Column(db.String(255), nullable=False)
|
|
lead_text = db.Column(db.Text, nullable=False)
|
|
description_1 = db.Column(db.Text)
|
|
description_2 = db.Column(db.Text)
|
|
years_experience = db.Column(db.Integer, default=7)
|
|
cv_url = db.Column(db.String(500))
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
def to_dict(self):
|
|
return {
|
|
'id': self.id,
|
|
'title': self.title,
|
|
'lead_text': self.lead_text,
|
|
'description_1': self.description_1,
|
|
'description_2': self.description_2,
|
|
'years_experience': self.years_experience,
|
|
'cv_url': self.cv_url
|
|
}
|
|
|
|
|
|
class Skill(db.Model):
|
|
"""Store technical skills/technologies"""
|
|
__tablename__ = 'skills'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
name = db.Column(db.String(100), nullable=False)
|
|
icon_class = db.Column(db.String(100), nullable=False)
|
|
category = db.Column(db.String(50)) # OS, Language, Framework, Tool, etc.
|
|
proficiency_level = db.Column(db.Integer) # 1-5 (optional)
|
|
display_order = db.Column(db.Integer, default=0)
|
|
is_active = db.Column(db.Boolean, default=True)
|
|
|
|
def to_dict(self):
|
|
return {
|
|
'id': self.id,
|
|
'name': self.name,
|
|
'icon_class': self.icon_class,
|
|
'category': self.category,
|
|
'proficiency_level': self.proficiency_level,
|
|
'display_order': self.display_order
|
|
}
|
|
|
|
|
|
class Project(db.Model):
|
|
"""Store portfolio projects"""
|
|
__tablename__ = 'projects'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
title = db.Column(db.String(255), nullable=False)
|
|
description = db.Column(db.Text, nullable=False)
|
|
image_url = db.Column(db.String(500))
|
|
demo_url = db.Column(db.String(500))
|
|
github_url = db.Column(db.String(500))
|
|
display_order = db.Column(db.Integer, default=0)
|
|
animation_delay = db.Column(db.String(10), default='0s') # e.g., '0.2s'
|
|
is_published = db.Column(db.Boolean, default=True)
|
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
# Relationship
|
|
tags = db.relationship('ProjectTag', backref='project', lazy=True, cascade='all, delete-orphan')
|
|
|
|
def to_dict(self):
|
|
return {
|
|
'id': self.id,
|
|
'title': self.title,
|
|
'description': self.description,
|
|
'image_url': self.image_url,
|
|
'demo_url': self.demo_url,
|
|
'github_url': self.github_url,
|
|
'display_order': self.display_order,
|
|
'animation_delay': self.animation_delay,
|
|
'is_published': self.is_published,
|
|
'tags': [tag.to_dict() for tag in self.tags]
|
|
}
|
|
|
|
|
|
class ProjectTag(db.Model):
|
|
"""Store tags/badges for projects"""
|
|
__tablename__ = 'project_tags'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
project_id = db.Column(db.Integer, db.ForeignKey('projects.id'), nullable=False)
|
|
name = db.Column(db.String(50), nullable=False)
|
|
color_class = db.Column(db.String(50), default='bg-primary') # Bootstrap badge classes
|
|
display_order = db.Column(db.Integer, default=0)
|
|
|
|
def to_dict(self):
|
|
return {
|
|
'id': self.id,
|
|
'name': self.name,
|
|
'color_class': self.color_class,
|
|
'display_order': self.display_order
|
|
}
|
|
|
|
|
|
class SocialLink(db.Model):
|
|
"""Store social media and profile links"""
|
|
__tablename__ = 'social_links'
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
platform_name = db.Column(db.String(100), nullable=False)
|
|
url = db.Column(db.String(500), nullable=False)
|
|
icon_class = db.Column(db.String(100), nullable=False)
|
|
display_order = db.Column(db.Integer, default=0)
|
|
animation_delay = db.Column(db.String(10), default='0s') # e.g., '0.1s'
|
|
is_active = db.Column(db.Boolean, default=True)
|
|
|
|
def to_dict(self):
|
|
return {
|
|
'id': self.id,
|
|
'platform_name': self.platform_name,
|
|
'url': self.url,
|
|
'icon_class': self.icon_class,
|
|
'display_order': self.display_order,
|
|
'animation_delay': self.animation_delay
|
|
}
|