Personal portfolio website for RETEC, built with Flask. Features an admin dashboard, project showcase, blog, testimonials, analytics, and a black-and-white editorial design.
- Backend: Python, Flask, SQLAlchemy, SQLite (dev) / PostgreSQL (prod)
- Frontend: Jinja2 templates, vanilla CSS, vanilla JS
- Icons: Font Awesome 6
- Typography: Inter + DM Serif Display
- Portfolio project showcase with GitHub API fallback
- Blog with admin CRUD, slugs, and HTML content
- Testimonial management
- Analytics (page views, section interest tracking)
- Fun fact ticker (floating bottom-left)
- WhatsApp contact button (floating bottom-right)
- Contact form with spam protection (honeypot + rate limiting)
- Enquiry tracking: submissions stored and listed at
/admin/enquiries, exportable as CSV - Contact alerts by email with a cumulative enquiry CSV attached (Brevo)
- SMTP email support
- CV page
- Category-based project filtering
- SEO meta tags + Open Graph
- Page transition animations
- Admin panel at
/admin
pip install -r requirements.txt
cp .env.example .env
# Set SECRET_KEY, INITIAL_ADMIN_USERNAME, and a 12+ character INITIAL_ADMIN_PASSWORD in .env
python app.pyThe app runs on http://localhost:5000. The first admin account is created only when INITIAL_ADMIN_USERNAME and INITIAL_ADMIN_PASSWORD are configured; existing admin accounts are not changed.
- Push this repo to GitHub
- On Render: New > Web Service → connect repo
- Build Command:
pip install -r requirements.txt - Start Command:
gunicorn app:app - Add a Render PostgreSQL database and set
DATABASE_URLenv var - Set
SECRET_KEY,INITIAL_ADMIN_USERNAME, andINITIAL_ADMIN_PASSWORDenv vars - Set
RATELIMIT_STORAGE_URIto a shared Redis URL for limits across workers - Deploy
| Variable | Description |
|---|---|
DATABASE_URL |
PostgreSQL connection string (Render) |
SECRET_KEY |
Flask session secret key |
INITIAL_ADMIN_USERNAME |
Username for the first admin account on an empty database |
INITIAL_ADMIN_PASSWORD |
First admin password; must be at least 12 characters |
RATELIMIT_STORAGE_URI |
Flask-Limiter storage URL; use shared Redis in production |
MAIL_SERVER |
SMTP server (optional) |
MAIL_PORT |
SMTP port (default 587) |
MAIL_USE_TLS |
Use STARTTLS, usually true for port 587 |
MAIL_USE_SSL |
Use SMTP SSL, usually true for port 465 |
MAIL_USERNAME |
SMTP username |
MAIL_PASSWORD |
SMTP password |
MAIL_FROM |
Sender email address |
MAIL_TO |
Contact form recipient |
BREVO_API_KEY |
Brevo API key for contact alerts |
SMS_NOTIFY_TO |
Unused. SMS alerts were replaced by the CSV-attachment email |
Every contact form submission is stored in the enquiry table and listed at
/admin/enquiries, with the full project message one click away. The same data
is available as CSV from /admin/enquiries/export.csv.
Each new enquiry also emails MAIL_TO with a cumulative CSV attached
(retec-enquiries-YYYY-MM-DD.csv) containing every enquiry received so far, so
the dataset is always recoverable from your inbox. This replaced the previous
SMS alert; send_sms_notification() is kept in app.py but is no longer called.
Copy .env.example to .env, then fill in your provider details. For Gmail, enable 2-Step Verification and create an App Password. Use:
MAIL_SERVER=smtp.gmail.com
MAIL_PORT=587
MAIL_USE_TLS=true
MAIL_USE_SSL=false
MAIL_USERNAME=your-email@gmail.com
MAIL_PASSWORD=your-google-app-password
MAIL_FROM=your-email@gmail.com
MAIL_TO=contact.retec@gmail.com