Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/flask/flask3-social-media/.flaskenv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FLASK_APP=microblog.py
FLASK_DEBUG=1
FLASK_RUN_PORT=5001
2 changes: 2 additions & 0 deletions apps/flask/flask3-social-media/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
* text=auto
*.sh text eol=lf
40 changes: 40 additions & 0 deletions apps/flask/flask3-social-media/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
*.py[cod]

# C extensions
*.so

# Packages
*.egg
*.egg-info
dist
build
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
__pycache__

# Installer logs
pip-log.txt

# Unit test / coverage reports
.coverage
.tox
nosetests.xml

# Translations
*.mo

# Mr Developer
.mr.developer.cfg
.project
.pydevproject

venv
app.db
microblog.log*
16 changes: 16 additions & 0 deletions apps/flask/flask3-social-media/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM python:slim

COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
RUN pip install gunicorn pymysql cryptography

COPY app app
COPY migrations migrations
COPY microblog.py config.py boot.sh ./
RUN chmod a+x boot.sh

ENV FLASK_APP microblog.py
RUN flask translate compile

EXPOSE 5000
ENTRYPOINT ["./boot.sh"]
21 changes: 21 additions & 0 deletions apps/flask/flask3-social-media/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2017 Miguel Grinberg

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

2 changes: 2 additions & 0 deletions apps/flask/flask3-social-media/Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
web: flask db upgrade; flask translate compile; gunicorn microblog:app
worker: rq worker microblog-tasks
155 changes: 155 additions & 0 deletions apps/flask/flask3-social-media/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Microblog

A full-featured social media microblogging platform built with Flask 3. Users can post short messages, follow other users, send private messages, and more.

## Running the App

### Prerequisites

- Python 3.10+
- SQLite (included with Python, used by default)
- Redis (optional)
- Elasticsearch (optional)

### Installation

1. Create and activate a virtual environment:

```bash
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```

2. Install dependencies:

```bash
pip install -r requirements.txt
```

3. Set up environment variables (create a `.env` file):

```bash
SECRET_KEY=your-secret-key
# DATABASE_URL=postgresql://... # Optional, defaults to SQLite (app.db)
MAIL_SERVER=smtp.example.com # Optional
MAIL_PORT=587
MAIL_USE_TLS=true
MAIL_USERNAME=your-email
MAIL_PASSWORD=your-password
REDIS_URL=redis://localhost:6379 # Optional
ELASTICSEARCH_URL=http://localhost:9200 # Optional
```

> **Note:** By default, the app uses SQLite (`app.db` in the project root). No database setup is required for local development.

4. Initialize the database:

```bash
flask db upgrade
```

5. Run the development server:

```bash
flask run
```

The app will be available at `http://localhost:5000`.

### Docker Deployment

The app includes a `Dockerfile` and `boot.sh` script for containerized deployment using Gunicorn.

---

## Application Structure

```
├── microblog.py # Application entry point
├── config.py # Configuration settings
├── requirements.txt # Python dependencies
├── app/
│ ├── __init__.py # App factory and extension initialization
│ ├── models.py # SQLAlchemy database models
│ ├── search.py # Elasticsearch integration
│ ├── tasks.py # Background task definitions (RQ)
│ ├── email.py # Email sending utilities
│ ├── translate.py # Translation service integration
│ ├── cli.py # Custom CLI commands
│ ├── api/ # REST API blueprint
│ │ ├── auth.py # Token authentication
│ │ ├── users.py # User endpoints
│ │ ├── tokens.py # Token management
│ │ └── errors.py # API error handlers
│ ├── auth/ # Authentication blueprint
│ │ ├── routes.py # Login, register, password reset
│ │ ├── forms.py # WTForms definitions
│ │ └── email.py # Auth-related emails
│ ├── main/ # Main application blueprint
│ │ ├── routes.py # Homepage, user profiles, posts, messaging
│ │ └── forms.py # Post and profile forms
│ ├── errors/ # Error handling blueprint
│ │ └── handlers.py # 404, 500 error pages
│ ├── templates/ # Jinja2 templates
│ └── translations/ # i18n message catalogs (en, es)
└── migrations/ # Alembic database migrations
```

## Features

### User System
- Registration with email validation
- Login/logout with Flask-Login session management
- Password reset via email (JWT tokens)
- User profiles with avatars (Gravatar)
- "About me" bios and last seen timestamps

### Social Features
- Create short posts (140 characters)
- Follow/unfollow other users
- Timeline feed showing posts from followed users
- Private messaging between users
- Real-time notifications

### API
- RESTful API at `/api/` with token-based authentication
- Endpoints for user CRUD, followers, and following lists
- Paginated responses with hypermedia links

### Search
- Full-text search powered by Elasticsearch (optional)
- Automatic indexing of posts on create/update/delete

### Background Tasks
- Redis Queue (RQ) for async job processing
- Export posts feature runs as background task

### Internationalization
- Multi-language support via Flask-Babel
- English and Spanish translations included
- Language auto-detection from browser preferences

## Database Models

| Model | Description |
|-------|-------------|
| `User` | User accounts with authentication, profiles, and social relationships |
| `Post` | Short text posts with timestamps and language detection |
| `Message` | Private messages between users |
| `Notification` | Real-time notification payloads |
| `Task` | Background task tracking for RQ jobs |

## Key Dependencies

- **Flask** - Web framework
- **Flask-SQLAlchemy** - Database ORM (SQLite by default, PostgreSQL supported)
- **Flask-Migrate** - Database migrations (Alembic)
- **Flask-Login** - User session management
- **Flask-WTF** - Form handling and CSRF protection
- **Flask-Mail** - Email sending
- **Flask-Babel** - Internationalization
- **Flask-Moment** - Client-side timestamp formatting
- **Flask-HTTPAuth** - API authentication
- **PyJWT** - JSON Web Tokens for password reset
- **Redis / RQ** - Background task queue
- **Elasticsearch** - Full-text search
7 changes: 7 additions & 0 deletions apps/flask/flask3-social-media/Vagrantfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/jammy64"
config.vm.network "private_network", ip: "192.168.56.10"
config.vm.provider "virtualbox" do |vb|
vb.memory = "2048"
end
end
110 changes: 110 additions & 0 deletions apps/flask/flask3-social-media/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import logging
from logging.handlers import SMTPHandler, RotatingFileHandler
import os
from flask import Flask, request, current_app
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_mail import Mail
from flask_moment import Moment
from flask_babel import Babel, lazy_gettext as _l
try:
from elasticsearch import Elasticsearch
except ImportError:
Elasticsearch = None
try:
from redis import Redis
import rq
except ImportError:
Redis = None
rq = None
from config import Config


def get_locale():
return request.accept_languages.best_match(current_app.config['LANGUAGES'])


db = SQLAlchemy()
migrate = Migrate()
login = LoginManager()
login.login_view = 'auth.login'
login.login_message = _l('Please log in to access this page.')
mail = Mail()
moment = Moment()
babel = Babel()


def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)

db.init_app(app)
migrate.init_app(app, db)
login.init_app(app)
mail.init_app(app)
moment.init_app(app)
babel.init_app(app, locale_selector=get_locale)
app.elasticsearch = Elasticsearch([app.config['ELASTICSEARCH_URL']]) \
if Elasticsearch and app.config['ELASTICSEARCH_URL'] else None
if Redis and rq:
app.redis = Redis.from_url(app.config['REDIS_URL'])
app.task_queue = rq.Queue('microblog-tasks', connection=app.redis)
else:
app.redis = None
app.task_queue = None

from app.errors import bp as errors_bp
app.register_blueprint(errors_bp)

from app.auth import bp as auth_bp
app.register_blueprint(auth_bp, url_prefix='/auth')

from app.main import bp as main_bp
app.register_blueprint(main_bp)

from app.cli import bp as cli_bp
app.register_blueprint(cli_bp)

from app.api import bp as api_bp
app.register_blueprint(api_bp, url_prefix='/api')

if not app.debug and not app.testing:
if app.config['MAIL_SERVER']:
auth = None
if app.config['MAIL_USERNAME'] or app.config['MAIL_PASSWORD']:
auth = (app.config['MAIL_USERNAME'],
app.config['MAIL_PASSWORD'])
secure = None
if app.config['MAIL_USE_TLS']:
secure = ()
mail_handler = SMTPHandler(
mailhost=(app.config['MAIL_SERVER'], app.config['MAIL_PORT']),
fromaddr='no-reply@' + app.config['MAIL_SERVER'],
toaddrs=app.config['ADMINS'], subject='Microblog Failure',
credentials=auth, secure=secure)
mail_handler.setLevel(logging.ERROR)
app.logger.addHandler(mail_handler)

if app.config['LOG_TO_STDOUT']:
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.INFO)
app.logger.addHandler(stream_handler)
else:
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler('logs/microblog.log',
maxBytes=10240, backupCount=10)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s '
'[in %(pathname)s:%(lineno)d]'))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)

app.logger.setLevel(logging.INFO)
app.logger.info('Microblog startup')

return app


from app import models
5 changes: 5 additions & 0 deletions apps/flask/flask3-social-media/app/api/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from flask import Blueprint

bp = Blueprint('api', __name__)

from app.api import users, errors, tokens
Loading