final stage of the app

This commit is contained in:
2025-07-15 14:32:57 +03:00
parent 94f006d458
commit b94d2ebbd6
29 changed files with 1498 additions and 1124 deletions

76
.dockerignore Normal file
View File

@@ -0,0 +1,76 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.pyc
*.pyo
*.pyd
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# IDEs
.vscode/
.idea/
*.swp
*.swo
.DS_Store
Thumbs.db
# Git
.git/
.gitignore
# Documentation
README.md
*.md
docs/
# Test files
test_*.py
*_test.py
tests/
# Logs
*.log
logs/
# Temporary files
*.tmp
*.temp
.cache/
# Development files
.env.example
docker-compose.override.yml
# Generated QR codes and uploads (will be in volumes)
static/qr_codes/*.png
static/logos/*
!static/qr_codes/.gitkeep
!static/logos/.gitkeep
# Session data
flask_session/

24
.env.example Normal file
View File

@@ -0,0 +1,24 @@
# QR Code Manager Environment Configuration
# Copy this file to .env and customize as needed
# Security Settings
SECRET_KEY=your-super-secret-key-change-me-in-production
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin123
# Flask Configuration
FLASK_ENV=production
FLASK_DEBUG=false
# Server Configuration
HOST=0.0.0.0
PORT=5000
# Upload Configuration
MAX_CONTENT_LENGTH=16777216
# Default QR Code Settings
DEFAULT_QR_SIZE=10
DEFAULT_QR_BORDER=4
DEFAULT_FOREGROUND_COLOR=#000000
DEFAULT_BACKGROUND_COLOR=#FFFFFF

49
Dockerfile Normal file
View File

@@ -0,0 +1,49 @@
# Use Python 3.11 slim image
FROM python:3.11-slim
# Set working directory
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
libjpeg-dev \
zlib1g-dev \
libfreetype6-dev \
liblcms2-dev \
libopenjp2-7-dev \
libtiff5-dev \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements first for better caching
COPY requirements.txt .
# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY main.py .
COPY app/ ./app/
# Create necessary directories
RUN mkdir -p app/static/qr_codes app/static/logos flask_session
# Set environment variables
ENV FLASK_APP=main.py
ENV FLASK_ENV=production
ENV PYTHONUNBUFFERED=1
# Create non-root user for security
RUN useradd --create-home --shell /bin/bash app && \
chown -R app:app /app
USER app
# Expose port
EXPOSE 5000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import requests; requests.get('http://localhost:5000/health')" || exit 1
# Run the application
CMD ["python", "main.py"]

437
README.md
View File

@@ -1,267 +1,214 @@
# QR C### QR Code Types Suppo### Management Features
- **Preview** - Real-time preview of generated QR codes
- **Download** - Export QR codes as PNG images
- **History** - View and manage previously generated QR codes
- **Delete** - Remove unwanted QR codes
- **Copy** - Copy QR codes to clipboard
- **Dynamic Link Management** - ⭐ **NEW!** Create and edit link collections **Text** - Plain text QR codes
- **URL/Website** - Direct links to websites
- **Dynamic Link Page** - ⭐ **NEW!** Create a web page with manageable links
- **WiFi** - WiFi network connection details
- **Email** - Pre-filled email composition
- **Phone** - Direct phone number dialing
- **SMS** - Pre-filled SMS messages
- **vCard** - Digital contact cardsger
# QR Code Manager
A comprehensive Python web application for creating, customizing, and managing QR codes. This application provides functionality similar to popular QR code generation websites with additional features for local management.
A modern Flask web application for generating and managing QR codes with authentication and dynamic link pages.
## Features
## 🚀 Features
### QR Code Types Supported
- **Text**: Plain text QR codes
- **URL/Website**: Direct links to websites
- **WiFi**: WiFi network connection details
- **Email**: Pre-filled email composition
- **Phone**: Direct phone number dialing
- **SMS**: Pre-filled SMS messages
- **vCard**: Digital contact cards
- **Multiple QR Code Types**: Text, URL, WiFi, Email, SMS, vCard
- **Dynamic Link Pages**: Create collections of links accessible via QR codes
- **Admin Authentication**: Secure login with bcrypt password hashing
- **Customizable Styling**: Different QR code styles (square, rounded, circle)
- **Logo Integration**: Add custom logos to QR codes
- **Docker Deployment**: Production-ready containerization
- **Responsive Design**: Modern web interface that works on all devices
### Customization Options
- **Colors**: Customizable foreground and background colors
- **Styles**: Square, rounded, or circular module styles
- **Size**: Adjustable module size (5-15px per module)
- **Logo**: Option to add custom logos to QR codes
### Management Features
- **Preview**: Real-time preview of generated QR codes
- **Download**: Export QR codes as PNG images
- **History**: View and manage previously generated QR codes
- **Delete**: Remove unwanted QR codes
- **Copy**: Copy QR codes to clipboard
## Installation
1. **Clone or navigate to the project directory**:
```bash
cd /home/pi/Desktop/qr-code_manager
```
2. **Install dependencies**:
```bash
pip install -r requirements.txt
```
Or if using the virtual environment that was created:
```bash
/home/pi/Desktop/qr-code_manager/.venv/bin/pip install -r requirements.txt
```
## Usage
1. **Start the server**:
```bash
python app.py
```
Or with the virtual environment:
```bash
/home/pi/Desktop/qr-code_manager/.venv/bin/python app.py
```
2. **Access the application**:
Open your web browser and navigate to `http://localhost:5000`
3. **Create QR codes**:
- Select the type of QR code you want to create
- Fill in the required information
- Customize the appearance (colors, style, size)
- Click "Generate QR Code"
- Download or copy your QR code
4. **Use Dynamic Link Pages** ⭐ **NEW!**:
- Select "Dynamic Link Page" as the QR code type
- Enter a title and description for your link collection
- Generate the QR code
- Use the "Manage" button or edit URL to add/edit links
- Share the QR code - visitors will see your current link collection
- Update links anytime without changing the QR code!
## API Endpoints
The application provides a RESTful API for programmatic access:
### Generate QR Code
- **POST** `/api/generate`
- **Body**: JSON with QR code parameters
- **Response**: Generated QR code data and download URL
### Download QR Code
- **GET** `/api/download/<qr_id>`
- **Response**: PNG image file
### List QR Codes
- **GET** `/api/qr_codes`
- **Response**: JSON array of all generated QR codes
### Get QR Code Details
- **GET** `/api/qr_codes/<qr_id>`
- **Response**: JSON with QR code details
### Delete QR Code
- **DELETE** `/api/qr_codes/<qr_id>`
- **Response**: Success/error status
### Upload Logo
- **POST** `/api/upload_logo`
- **Body**: Multipart form with logo file
- **Response**: Logo path for use in QR generation
### Dynamic Link Pages ⭐ **NEW!**
#### Create Link Page
- **POST** `/api/create_link_page`
- **Body**: JSON with page title, description, and QR styling
- **Response**: QR code data, page URLs, and management links
#### Add Link to Page
- **POST** `/api/link_pages/<page_id>/links`
- **Body**: JSON with link title, URL, and description
- **Response**: Success/error status
#### Update Link
- **PUT** `/api/link_pages/<page_id>/links/<link_id>`
- **Body**: JSON with updated link data
- **Response**: Success/error status
#### Delete Link
- **DELETE** `/api/link_pages/<page_id>/links/<link_id>`
- **Response**: Success/error status
#### Get Link Page Data
- **GET** `/api/link_pages/<page_id>`
- **Response**: JSON with page and links data
#### View Public Link Page
- **GET** `/links/<page_id>`
- **Response**: HTML page displaying links
#### Edit Link Page
- **GET** `/edit/<page_id>`
- **Response**: HTML interface for managing links
## Example API Usage
### Generate a URL QR Code
```bash
curl -X POST http://localhost:5000/api/generate \
-H "Content-Type: application/json" \
-d '{
"type": "url",
"content": "https://github.com",
"foreground_color": "#000000",
"background_color": "#FFFFFF",
"style": "square",
"size": 10
}'
```
### Generate a WiFi QR Code
```bash
curl -X POST http://localhost:5000/api/generate \
-H "Content-Type: application/json" \
-d '{
"type": "wifi",
"wifi": {
"ssid": "MyNetwork",
"password": "MyPassword",
"security": "WPA"
},
"foreground_color": "#0066cc",
"background_color": "#ffffff"
}'
```
### Create a Dynamic Link Page ⭐ **NEW!**
```bash
curl -X POST http://localhost:5000/api/create_link_page \
-H "Content-Type: application/json" \
-d '{
"title": "My Resources",
"description": "Collection of useful links",
"foreground_color": "#1565c0",
"background_color": "#ffffff",
"style": "rounded"
}'
```
### Add Links to the Page
```bash
curl -X POST http://localhost:5000/api/link_pages/PAGE_ID/links \
-H "Content-Type: application/json" \
-d '{
"title": "GitHub",
"url": "https://github.com",
"description": "Code repository platform"
}'
```
## File Structure
## 📁 Project Structure
```
qr-code_manager/
├── app.py # Main Flask application
├── requirements.txt # Python dependencies
├── README.md # This file
├── templates/
│ └── index.html # Web interface
├── static/
│ ├── qr_codes/ # Generated QR code images
│ └── logos/ # Uploaded logo files
└── .venv/ # Virtual environment (if created)
├── README.md # Project documentation
├── main.py # Application entry point
├── Dockerfile # Docker container definition
├── docker-compose.yml # Docker orchestration
├── requirements.txt # Python dependencies
├── .env.example # Environment variables template
├── deploy.sh # Deployment script
├── app/ # Main application package
│ ├── __init__.py # Flask app factory
│ ├── templates/ # Jinja2 templates
│ │ ├── index.html # Main dashboard
│ │ ├── login.html # Authentication page
│ │ ├── link_page.html # Public link display
│ │ └── edit_links.html # Link management interface
│ ├── static/ # Static files
│ │ ├── qr_codes/ # Generated QR code images
│ │ └── logos/ # Uploaded logo files
│ ├── routes/ # Route handlers
│ │ ├── __init__.py # Route package
│ │ ├── main.py # Main page routes
│ │ ├── auth.py # Authentication routes
│ │ └── api.py # API endpoints
│ └── utils/ # Utility modules
│ ├── __init__.py # Utils package
│ ├── auth.py # Authentication utilities
│ ├── qr_generator.py # QR code generation
│ ├── link_manager.py # Dynamic link management
│ └── data_manager.py # Data storage utilities
```
## Dependencies
## 🛠️ Quick Start
- **Flask**: Web framework
- **qrcode[pil]**: QR code generation with PIL support
- **Pillow**: Image processing
- **flask-cors**: Cross-Origin Resource Sharing support
- **python-dotenv**: Environment variable management
### Method 1: Docker (Recommended)
## Security Considerations
1. **Clone and navigate to the project:**
```bash
git clone <your-repo-url>
cd qr-code_manager
```
- The application runs in debug mode by default - disable for production
- File uploads are stored locally - implement proper validation and storage limits
- QR codes are stored in memory - consider using a database for persistence
- The server accepts connections from all interfaces (0.0.0.0) - restrict for production
2. **Create environment file:**
```bash
cp .env.example .env
# Edit .env with your preferred settings
```
## Customization
3. **Deploy with Docker:**
```bash
./deploy.sh
```
### Adding New QR Code Types
1. Add the new type to the HTML select options
2. Create corresponding form fields in the HTML
3. Add processing logic in the `/api/generate` endpoint
4. Update the JavaScript to handle the new type
4. **Access the application:**
- Open http://localhost:5000
- Login with: admin / admin123 (change in production!)
### Styling
The application uses embedded CSS for easy customization. Modify the `<style>` section in `templates/index.html` to change the appearance.
### Method 2: Local Development
### Storage
Currently uses in-memory storage. To persist data:
1. Install a database library (SQLite, PostgreSQL, etc.)
2. Replace the `qr_codes_db` dictionary with database operations
3. Add database initialization code
1. **Set up Python environment:**
```bash
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
```
## Troubleshooting
2. **Install dependencies:**
```bash
pip install -r requirements.txt
```
3. **Run the application:**
```bash
python main.py
```
## 🔐 Authentication
- **Default Credentials**: admin / admin123
- **Environment Variables**:
- `ADMIN_USERNAME`: Set custom admin username
- `ADMIN_PASSWORD`: Set custom admin password
- `SECRET_KEY`: Set Flask secret key for sessions
## 🐳 Docker Deployment
The application is fully containerized with Docker:
- **Build and run**: `docker-compose up -d`
- **View logs**: `docker-compose logs -f`
- **Stop**: `docker-compose down`
- **Full rebuild**: `docker-compose up --build -d`
### Production Configuration
1. **Set environment variables in .env:**
```bash
FLASK_ENV=production
SECRET_KEY=your-super-secret-key-here
ADMIN_USERNAME=your-admin-username
ADMIN_PASSWORD=your-secure-password
```
2. **Deploy:**
```bash
docker-compose up -d
```
## 📱 Usage
### Generating QR Codes
1. **Login** to the admin interface
2. **Select QR type**: Text, URL, WiFi, Email, SMS, or vCard
3. **Fill in the details** for your chosen type
4. **Customize appearance**: Size, colors, style, border
5. **Add logo** (optional): Upload custom logo for branding
6. **Generate and download** your QR code
### Dynamic Link Pages
1. **Create a link page** from the main interface
2. **Add links** to your collection via the edit interface
3. **Share the QR code** that points to your link page
4. **Update links anytime** without changing the QR code
## 🛡️ Security Features
- **Password Hashing**: Uses bcrypt for secure password storage
- **Session Management**: Secure Flask sessions with signing
- **Authentication Required**: All admin functions require login
- **Docker Security**: Non-root user in container
- **Environment Variables**: Sensitive data via environment configuration
## 🔧 Development
### Project Architecture
The application follows a modular Flask structure:
- **App Factory Pattern**: Clean application initialization
- **Blueprint Organization**: Separate route modules for different features
- **Utility Modules**: Reusable components for QR generation, auth, etc.
- **Template Organization**: Structured Jinja2 templates
- **Static File Management**: Organized asset storage
### Adding New Features
1. **New Routes**: Add to appropriate blueprint in `app/routes/`
2. **New Utilities**: Create modules in `app/utils/`
3. **New Templates**: Add to `app/templates/`
4. **New Dependencies**: Update `requirements.txt`
## 📊 API Endpoints
- `POST /api/generate` - Generate QR code
- `GET /api/qr_codes` - List all QR codes
- `GET /api/qr_codes/{id}` - Get specific QR code
- `DELETE /api/qr_codes/{id}` - Delete QR code
- `POST /api/create_link_page` - Create dynamic link page
- `POST /api/link_pages/{id}/links` - Add link to page
- `PUT /api/link_pages/{id}/links/{link_id}` - Update link
- `DELETE /api/link_pages/{id}/links/{link_id}` - Delete link
## 🚨 Troubleshooting
### Common Issues
1. **Port already in use**: Change the port in `app.py` or stop the conflicting service
2. **Permission errors**: Ensure the application has write permissions to the static directories
3. **Missing dependencies**: Reinstall requirements with `pip install -r requirements.txt`
### Debug Mode
The application runs in debug mode, which provides detailed error messages and auto-reload functionality. Disable for production by setting `debug=False` in the `app.run()` call.
1. **Permission Denied**: Ensure Docker has proper permissions
2. **Port 5000 in use**: Change port in docker-compose.yml
3. **Authentication Failed**: Check admin credentials in .env
4. **Image Generation Failed**: Verify PIL/Pillow installation
## License
### Health Check
This project is open source. Feel free to modify and distribute according to your needs.
Visit `/health` to check application status:
```bash
curl http://localhost:5000/health
```
## 📝 License
This project is licensed under the MIT License - see the LICENSE file for details.
## 🤝 Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request
## 📞 Support
For support, please open an issue on GitHub or contact the development team.
---
**Made with ❤️ for easy QR code management**

505
app.py
View File

@@ -1,505 +0,0 @@
import os
import io
import base64
import json
import uuid
from datetime import datetime
from PIL import Image, ImageDraw
import qrcode
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.moduledrawers import RoundedModuleDrawer, CircleModuleDrawer, SquareModuleDrawer
from flask import Flask, request, jsonify, send_file, render_template
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
# Configuration
UPLOAD_FOLDER = 'static/qr_codes'
LOGOS_FOLDER = 'static/logos'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(LOGOS_FOLDER, exist_ok=True)
# In-memory storage for QR codes (in production, use a database)
qr_codes_db = {}
# In-memory storage for dynamic link pages
link_pages_db = {}
class QRCodeGenerator:
def __init__(self):
self.default_settings = {
'size': 10,
'border': 4,
'error_correction': qrcode.constants.ERROR_CORRECT_M,
'foreground_color': '#000000',
'background_color': '#FFFFFF',
'style': 'square'
}
def generate_qr_code(self, data, settings=None):
"""Generate QR code with custom settings"""
if settings is None:
settings = self.default_settings.copy()
else:
merged_settings = self.default_settings.copy()
merged_settings.update(settings)
settings = merged_settings
# Create QR code instance
qr = qrcode.QRCode(
version=1,
error_correction=settings['error_correction'],
box_size=settings['size'],
border=settings['border'],
)
qr.add_data(data)
qr.make(fit=True)
# For styled QR codes with custom module drawer
if settings['style'] != 'square':
# Choose module drawer based on style
module_drawer = None
if settings['style'] == 'rounded':
module_drawer = RoundedModuleDrawer()
elif settings['style'] == 'circle':
module_drawer = CircleModuleDrawer()
# Generate the styled image
img = qr.make_image(
image_factory=StyledPilImage,
module_drawer=module_drawer,
fill_color=settings['foreground_color'],
back_color=settings['background_color']
)
else:
# Generate standard image with custom colors
img = qr.make_image(
fill_color=settings['foreground_color'],
back_color=settings['background_color']
)
return img
def add_logo(self, qr_img, logo_path, logo_size_ratio=0.2):
"""Add logo to QR code"""
try:
logo = Image.open(logo_path)
# Calculate logo size
qr_width, qr_height = qr_img.size
logo_size = int(min(qr_width, qr_height) * logo_size_ratio)
# Resize logo
logo = logo.resize((logo_size, logo_size), Image.Resampling.LANCZOS)
# Create a white background for the logo
logo_bg = Image.new('RGB', (logo_size + 20, logo_size + 20), 'white')
logo_bg.paste(logo, (10, 10))
# Calculate position to center the logo
logo_pos = (
(qr_width - logo_bg.width) // 2,
(qr_height - logo_bg.height) // 2
)
# Paste logo onto QR code
qr_img.paste(logo_bg, logo_pos)
return qr_img
except Exception as e:
print(f"Error adding logo: {e}")
return qr_img
class LinkPageManager:
def __init__(self):
pass
def create_link_page(self, title="My Links", description="Collection of useful links"):
"""Create a new dynamic link page"""
page_id = str(uuid.uuid4())
page_data = {
'id': page_id,
'title': title,
'description': description,
'links': [],
'created_at': datetime.now().isoformat(),
'updated_at': datetime.now().isoformat(),
'view_count': 0
}
link_pages_db[page_id] = page_data
return page_id
def add_link(self, page_id, title, url, description=""):
"""Add a link to a page"""
if page_id not in link_pages_db:
return False
link_data = {
'id': str(uuid.uuid4()),
'title': title,
'url': url if url.startswith(('http://', 'https://')) else f'https://{url}',
'description': description,
'created_at': datetime.now().isoformat()
}
link_pages_db[page_id]['links'].append(link_data)
link_pages_db[page_id]['updated_at'] = datetime.now().isoformat()
return True
def update_link(self, page_id, link_id, title=None, url=None, description=None):
"""Update a specific link"""
if page_id not in link_pages_db:
return False
for link in link_pages_db[page_id]['links']:
if link['id'] == link_id:
if title is not None:
link['title'] = title
if url is not None:
link['url'] = url if url.startswith(('http://', 'https://')) else f'https://{url}'
if description is not None:
link['description'] = description
link_pages_db[page_id]['updated_at'] = datetime.now().isoformat()
return True
return False
def delete_link(self, page_id, link_id):
"""Delete a specific link"""
if page_id not in link_pages_db:
return False
links = link_pages_db[page_id]['links']
link_pages_db[page_id]['links'] = [link for link in links if link['id'] != link_id]
link_pages_db[page_id]['updated_at'] = datetime.now().isoformat()
return True
def increment_view_count(self, page_id):
"""Increment view count for a page"""
if page_id in link_pages_db:
link_pages_db[page_id]['view_count'] += 1
# Initialize managers
qr_generator = QRCodeGenerator()
link_manager = LinkPageManager()
@app.route('/')
def index():
"""Serve the main page"""
return render_template('index.html')
@app.route('/api/generate', methods=['POST'])
def generate_qr():
"""Generate QR code API endpoint"""
try:
data = request.json
# Extract QR code content
qr_type = data.get('type', 'text')
content = data.get('content', '')
# Process content based on type
if qr_type == 'url':
qr_content = content if content.startswith(('http://', 'https://')) else f'https://{content}'
elif qr_type == 'wifi':
wifi_data = data.get('wifi', {})
qr_content = f"WIFI:T:{wifi_data.get('security', 'WPA')};S:{wifi_data.get('ssid', '')};P:{wifi_data.get('password', '')};H:{wifi_data.get('hidden', 'false')};;"
elif qr_type == 'email':
email_data = data.get('email', {})
qr_content = f"mailto:{email_data.get('email', '')}?subject={email_data.get('subject', '')}&body={email_data.get('body', '')}"
elif qr_type == 'phone':
qr_content = f"tel:{content}"
elif qr_type == 'sms':
sms_data = data.get('sms', {})
qr_content = f"smsto:{sms_data.get('phone', '')}:{sms_data.get('message', '')}"
elif qr_type == 'vcard':
vcard_data = data.get('vcard', {})
qr_content = f"""BEGIN:VCARD
VERSION:3.0
FN:{vcard_data.get('name', '')}
ORG:{vcard_data.get('organization', '')}
TEL:{vcard_data.get('phone', '')}
EMAIL:{vcard_data.get('email', '')}
URL:{vcard_data.get('website', '')}
END:VCARD"""
else: # text
qr_content = content
# Extract styling options
settings = {
'size': data.get('size', 10),
'border': data.get('border', 4),
'foreground_color': data.get('foreground_color', '#000000'),
'background_color': data.get('background_color', '#FFFFFF'),
'style': data.get('style', 'square')
}
# Generate QR code
qr_img = qr_generator.generate_qr_code(qr_content, settings)
# Add logo if provided
logo_path = data.get('logo_path')
if logo_path and os.path.exists(logo_path):
qr_img = qr_generator.add_logo(qr_img, logo_path)
# Convert to base64
img_buffer = io.BytesIO()
qr_img.save(img_buffer, format='PNG')
img_buffer.seek(0)
img_base64 = base64.b64encode(img_buffer.getvalue()).decode()
# Save QR code record
qr_id = str(uuid.uuid4())
qr_record = {
'id': qr_id,
'type': qr_type,
'content': qr_content,
'settings': settings,
'created_at': datetime.now().isoformat(),
'image_data': img_base64
}
qr_codes_db[qr_id] = qr_record
# Save image file
img_path = os.path.join(UPLOAD_FOLDER, f'{qr_id}.png')
qr_img.save(img_path)
return jsonify({
'success': True,
'qr_id': qr_id,
'image_data': f'data:image/png;base64,{img_base64}',
'download_url': f'/api/download/{qr_id}'
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/download/<qr_id>')
def download_qr(qr_id):
"""Download QR code"""
try:
img_path = os.path.join(UPLOAD_FOLDER, f'{qr_id}.png')
if os.path.exists(img_path):
return send_file(img_path, as_attachment=True, download_name=f'qr_code_{qr_id}.png')
else:
return jsonify({'error': 'QR code not found'}), 404
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/qr_codes')
def list_qr_codes():
"""List all generated QR codes"""
qr_list = []
for qr_id, qr_data in qr_codes_db.items():
qr_list.append({
'id': qr_id,
'type': qr_data['type'],
'created_at': qr_data['created_at'],
'preview': f'data:image/png;base64,{qr_data["image_data"]}'
})
return jsonify(qr_list)
@app.route('/api/qr_codes/<qr_id>')
def get_qr_code(qr_id):
"""Get specific QR code details"""
if qr_id in qr_codes_db:
return jsonify(qr_codes_db[qr_id])
else:
return jsonify({'error': 'QR code not found'}), 404
@app.route('/api/qr_codes/<qr_id>', methods=['DELETE'])
def delete_qr_code(qr_id):
"""Delete QR code"""
try:
if qr_id in qr_codes_db:
# Remove from database
del qr_codes_db[qr_id]
# Remove image file
img_path = os.path.join(UPLOAD_FOLDER, f'{qr_id}.png')
if os.path.exists(img_path):
os.remove(img_path)
return jsonify({'success': True})
else:
return jsonify({'error': 'QR code not found'}), 404
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/upload_logo', methods=['POST'])
def upload_logo():
"""Upload logo for QR code"""
try:
if 'logo' not in request.files:
return jsonify({'error': 'No logo file provided'}), 400
file = request.files['logo']
if file.filename == '':
return jsonify({'error': 'No file selected'}), 400
# Save logo
logo_id = str(uuid.uuid4())
logo_extension = file.filename.rsplit('.', 1)[1].lower()
logo_filename = f'{logo_id}.{logo_extension}'
logo_path = os.path.join(LOGOS_FOLDER, logo_filename)
file.save(logo_path)
return jsonify({
'success': True,
'logo_path': logo_path,
'logo_id': logo_id
})
except Exception as e:
return jsonify({'error': str(e)}), 500
# Dynamic Link Pages API Routes
@app.route('/api/create_link_page', methods=['POST'])
def create_link_page():
"""Create a new dynamic link page and QR code"""
try:
data = request.json
title = data.get('title', 'My Links')
description = data.get('description', 'Collection of useful links')
# Create the link page
page_id = link_manager.create_link_page(title, description)
# Create QR code pointing to the link page
page_url = f"{request.url_root}links/{page_id}"
settings = {
'size': data.get('size', 10),
'border': data.get('border', 4),
'foreground_color': data.get('foreground_color', '#000000'),
'background_color': data.get('background_color', '#FFFFFF'),
'style': data.get('style', 'square')
}
# Generate QR code
qr_img = qr_generator.generate_qr_code(page_url, settings)
# Convert to base64
img_buffer = io.BytesIO()
qr_img.save(img_buffer, format='PNG')
img_buffer.seek(0)
img_base64 = base64.b64encode(img_buffer.getvalue()).decode()
# Save QR code record
qr_id = str(uuid.uuid4())
qr_record = {
'id': qr_id,
'type': 'link_page',
'content': page_url,
'page_id': page_id,
'settings': settings,
'created_at': datetime.now().isoformat(),
'image_data': img_base64
}
qr_codes_db[qr_id] = qr_record
# Save image file
img_path = os.path.join(UPLOAD_FOLDER, f'{qr_id}.png')
qr_img.save(img_path)
return jsonify({
'success': True,
'qr_id': qr_id,
'page_id': page_id,
'page_url': page_url,
'edit_url': f"{request.url_root}edit/{page_id}",
'image_data': f'data:image/png;base64,{img_base64}',
'download_url': f'/api/download/{qr_id}'
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/link_pages/<page_id>/links', methods=['POST'])
def add_link_to_page(page_id):
"""Add a link to a page"""
try:
data = request.json
title = data.get('title', '')
url = data.get('url', '')
description = data.get('description', '')
if not title or not url:
return jsonify({'error': 'Title and URL are required'}), 400
success = link_manager.add_link(page_id, title, url, description)
if success:
return jsonify({'success': True})
else:
return jsonify({'error': 'Page not found'}), 404
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/link_pages/<page_id>/links/<link_id>', methods=['PUT'])
def update_link_in_page(page_id, link_id):
"""Update a link in a page"""
try:
data = request.json
title = data.get('title')
url = data.get('url')
description = data.get('description')
success = link_manager.update_link(page_id, link_id, title, url, description)
if success:
return jsonify({'success': True})
else:
return jsonify({'error': 'Page or link not found'}), 404
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/link_pages/<page_id>/links/<link_id>', methods=['DELETE'])
def delete_link_from_page(page_id, link_id):
"""Delete a link from a page"""
try:
success = link_manager.delete_link(page_id, link_id)
if success:
return jsonify({'success': True})
else:
return jsonify({'error': 'Page or link not found'}), 404
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/link_pages/<page_id>')
def get_link_page(page_id):
"""Get link page data"""
if page_id in link_pages_db:
return jsonify(link_pages_db[page_id])
else:
return jsonify({'error': 'Page not found'}), 404
@app.route('/links/<page_id>')
def view_link_page(page_id):
"""Display the public link page"""
if page_id not in link_pages_db:
return "Page not found", 404
link_manager.increment_view_count(page_id)
page_data = link_pages_db[page_id]
return render_template('link_page.html', page=page_data)
@app.route('/edit/<page_id>')
def edit_link_page(page_id):
"""Display the edit interface for the link page"""
if page_id not in link_pages_db:
return "Page not found", 404
page_data = link_pages_db[page_id]
return render_template('edit_links.html', page=page_data)
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)

37
app/__init__.py Normal file
View File

@@ -0,0 +1,37 @@
"""
QR Code Manager Flask Application
A modern Flask web application for generating and managing QR codes with authentication
"""
import os
from flask import Flask
from flask_cors import CORS
from flask_session import Session
from app.utils.auth import init_admin
def create_app():
"""Create and configure the Flask application"""
app = Flask(__name__)
# Configuration
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'your-secret-key-change-in-production')
app.config['SESSION_TYPE'] = 'filesystem'
app.config['SESSION_PERMANENT'] = False
app.config['SESSION_USE_SIGNER'] = True
# Initialize CORS
CORS(app)
# Initialize session
Session(app)
# Initialize admin user
init_admin()
# Register blueprints
from app.routes import main, api, auth
app.register_blueprint(main.bp)
app.register_blueprint(api.bp, url_prefix='/api')
app.register_blueprint(auth.bp)
return app

7
app/routes/__init__.py Normal file
View File

@@ -0,0 +1,7 @@
"""
Route modules for QR Code Manager
"""
from . import main, api, auth
__all__ = ['main', 'api', 'auth']

304
app/routes/api.py Normal file
View File

@@ -0,0 +1,304 @@
"""
API routes for QR Code Manager
"""
import os
import io
import base64
import uuid
from datetime import datetime
from flask import Blueprint, request, jsonify, send_file
from app.utils.auth import login_required
from app.utils.qr_generator import QRCodeGenerator
from app.utils.link_manager import LinkPageManager
from app.utils.data_manager import QRDataManager
bp = Blueprint('api', __name__)
# Initialize managers
qr_generator = QRCodeGenerator()
link_manager = LinkPageManager()
data_manager = QRDataManager()
# Configuration for file uploads
UPLOAD_FOLDER = 'app/static/qr_codes'
LOGOS_FOLDER = 'app/static/logos'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(LOGOS_FOLDER, exist_ok=True)
@bp.route('/generate', methods=['POST'])
@login_required
def generate_qr():
"""Generate QR code API endpoint"""
try:
data = request.json
# Extract QR code content
qr_type = data.get('type', 'text')
content = data.get('content', '')
# Process content based on type
if qr_type == 'url':
qr_content = content if content.startswith(('http://', 'https://')) else f'https://{content}'
elif qr_type == 'wifi':
wifi_data = data.get('wifi', {})
qr_content = f"WIFI:T:{wifi_data.get('security', 'WPA')};S:{wifi_data.get('ssid', '')};P:{wifi_data.get('password', '')};H:{wifi_data.get('hidden', 'false')};;"
elif qr_type == 'email':
email_data = data.get('email', {})
qr_content = f"mailto:{email_data.get('email', '')}?subject={email_data.get('subject', '')}&body={email_data.get('body', '')}"
elif qr_type == 'phone':
qr_content = f"tel:{content}"
elif qr_type == 'sms':
sms_data = data.get('sms', {})
qr_content = f"smsto:{sms_data.get('phone', '')}:{sms_data.get('message', '')}"
elif qr_type == 'vcard':
vcard_data = data.get('vcard', {})
qr_content = f"""BEGIN:VCARD
VERSION:3.0
FN:{vcard_data.get('name', '')}
ORG:{vcard_data.get('organization', '')}
TEL:{vcard_data.get('phone', '')}
EMAIL:{vcard_data.get('email', '')}
URL:{vcard_data.get('website', '')}
END:VCARD"""
else: # text
qr_content = content
# Extract styling options
settings = {
'size': data.get('size', 10),
'border': data.get('border', 4),
'foreground_color': data.get('foreground_color', '#000000'),
'background_color': data.get('background_color', '#FFFFFF'),
'style': data.get('style', 'square')
}
# Generate QR code
qr_img = qr_generator.generate_qr_code(qr_content, settings)
# Add logo if provided
logo_path = data.get('logo_path')
if logo_path and os.path.exists(logo_path):
qr_img = qr_generator.add_logo(qr_img, logo_path)
# Convert to base64
img_buffer = io.BytesIO()
qr_img.save(img_buffer, format='PNG')
img_buffer.seek(0)
img_base64 = base64.b64encode(img_buffer.getvalue()).decode()
# Save QR code record
qr_id = data_manager.save_qr_record(qr_type, qr_content, settings, img_base64)
# Save image file
img_path = os.path.join(UPLOAD_FOLDER, f'{qr_id}.png')
qr_img.save(img_path)
return jsonify({
'success': True,
'qr_id': qr_id,
'image_data': f'data:image/png;base64,{img_base64}',
'download_url': f'/api/download/{qr_id}'
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@bp.route('/download/<qr_id>')
@login_required
def download_qr(qr_id):
"""Download QR code"""
try:
img_path = os.path.join(UPLOAD_FOLDER, f'{qr_id}.png')
if os.path.exists(img_path):
return send_file(img_path, as_attachment=True, download_name=f'qr_code_{qr_id}.png')
else:
return jsonify({'error': 'QR code not found'}), 404
except Exception as e:
return jsonify({'error': str(e)}), 500
@bp.route('/qr_codes')
@login_required
def list_qr_codes():
"""List all generated QR codes"""
return jsonify(data_manager.list_qr_codes())
@bp.route('/qr_codes/<qr_id>')
@login_required
def get_qr_code(qr_id):
"""Get specific QR code details"""
qr_data = data_manager.get_qr_record(qr_id)
if qr_data:
return jsonify(qr_data)
else:
return jsonify({'error': 'QR code not found'}), 404
@bp.route('/qr_codes/<qr_id>', methods=['DELETE'])
@login_required
def delete_qr_code(qr_id):
"""Delete QR code"""
try:
if data_manager.qr_exists(qr_id):
# Remove from database
data_manager.delete_qr_record(qr_id)
# Remove image file
img_path = os.path.join(UPLOAD_FOLDER, f'{qr_id}.png')
if os.path.exists(img_path):
os.remove(img_path)
return jsonify({'success': True})
else:
return jsonify({'error': 'QR code not found'}), 404
except Exception as e:
return jsonify({'error': str(e)}), 500
@bp.route('/upload_logo', methods=['POST'])
@login_required
def upload_logo():
"""Upload logo for QR code"""
try:
if 'logo' not in request.files:
return jsonify({'error': 'No logo file provided'}), 400
file = request.files['logo']
if file.filename == '':
return jsonify({'error': 'No file selected'}), 400
# Save logo
logo_id = str(uuid.uuid4())
logo_extension = file.filename.rsplit('.', 1)[1].lower()
logo_filename = f'{logo_id}.{logo_extension}'
logo_path = os.path.join(LOGOS_FOLDER, logo_filename)
file.save(logo_path)
return jsonify({
'success': True,
'logo_path': logo_path,
'logo_id': logo_id
})
except Exception as e:
return jsonify({'error': str(e)}), 500
# Dynamic Link Pages API Routes
@bp.route('/create_link_page', methods=['POST'])
@login_required
def create_link_page():
"""Create a new dynamic link page and QR code"""
try:
data = request.json
title = data.get('title', 'My Links')
description = data.get('description', 'Collection of useful links')
# Create the link page
page_id = link_manager.create_link_page(title, description)
# Create QR code pointing to the link page
page_url = f"{request.url_root}links/{page_id}"
settings = {
'size': data.get('size', 10),
'border': data.get('border', 4),
'foreground_color': data.get('foreground_color', '#000000'),
'background_color': data.get('background_color', '#FFFFFF'),
'style': data.get('style', 'square')
}
# Generate QR code
qr_img = qr_generator.generate_qr_code(page_url, settings)
# Convert to base64
img_buffer = io.BytesIO()
qr_img.save(img_buffer, format='PNG')
img_buffer.seek(0)
img_base64 = base64.b64encode(img_buffer.getvalue()).decode()
# Save QR code record
qr_id = data_manager.save_qr_record('link_page', page_url, settings, img_base64, page_id)
# Save image file
img_path = os.path.join(UPLOAD_FOLDER, f'{qr_id}.png')
qr_img.save(img_path)
return jsonify({
'success': True,
'qr_id': qr_id,
'page_id': page_id,
'page_url': page_url,
'edit_url': f"{request.url_root}edit/{page_id}",
'image_data': f'data:image/png;base64,{img_base64}',
'download_url': f'/api/download/{qr_id}'
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@bp.route('/link_pages/<page_id>/links', methods=['POST'])
@login_required
def add_link_to_page(page_id):
"""Add a link to a page"""
try:
data = request.json
title = data.get('title', '')
url = data.get('url', '')
description = data.get('description', '')
if not title or not url:
return jsonify({'error': 'Title and URL are required'}), 400
success = link_manager.add_link(page_id, title, url, description)
if success:
return jsonify({'success': True})
else:
return jsonify({'error': 'Page not found'}), 404
except Exception as e:
return jsonify({'error': str(e)}), 500
@bp.route('/link_pages/<page_id>/links/<link_id>', methods=['PUT'])
@login_required
def update_link_in_page(page_id, link_id):
"""Update a link in a page"""
try:
data = request.json
title = data.get('title')
url = data.get('url')
description = data.get('description')
success = link_manager.update_link(page_id, link_id, title, url, description)
if success:
return jsonify({'success': True})
else:
return jsonify({'error': 'Page or link not found'}), 404
except Exception as e:
return jsonify({'error': str(e)}), 500
@bp.route('/link_pages/<page_id>/links/<link_id>', methods=['DELETE'])
@login_required
def delete_link_from_page(page_id, link_id):
"""Delete a link from a page"""
try:
success = link_manager.delete_link(page_id, link_id)
if success:
return jsonify({'success': True})
else:
return jsonify({'error': 'Page or link not found'}), 404
except Exception as e:
return jsonify({'error': str(e)}), 500
@bp.route('/link_pages/<page_id>')
@login_required
def get_link_page(page_id):
"""Get link page data"""
page_data = link_manager.get_page(page_id)
if page_data:
return jsonify(page_data)
else:
return jsonify({'error': 'Page not found'}), 404

34
app/routes/auth.py Normal file
View File

@@ -0,0 +1,34 @@
"""
Authentication routes for QR Code Manager
"""
from flask import Blueprint, request, render_template, session, redirect, url_for, flash
from app.utils.auth import get_admin_credentials, verify_password
bp = Blueprint('auth', __name__)
@bp.route('/login', methods=['GET', 'POST'])
def login():
"""Login page"""
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
admin_username, admin_password_hash = get_admin_credentials()
if username == admin_username and verify_password(password, admin_password_hash):
session['logged_in'] = True
session['username'] = username
flash('Successfully logged in!', 'success')
return redirect(url_for('main.index'))
else:
flash('Invalid username or password!', 'error')
return render_template('login.html')
@bp.route('/logout')
def logout():
"""Logout and clear session"""
session.clear()
flash('You have been logged out!', 'info')
return redirect(url_for('auth.login'))

46
app/routes/main.py Normal file
View File

@@ -0,0 +1,46 @@
"""
Main routes for QR Code Manager
"""
from flask import Blueprint, render_template
from app.utils.auth import login_required
from app.utils.link_manager import LinkPageManager
bp = Blueprint('main', __name__)
# Initialize manager
link_manager = LinkPageManager()
@bp.route('/')
@login_required
def index():
"""Serve the main page"""
return render_template('index.html')
@bp.route('/links/<page_id>')
def view_link_page(page_id):
"""Display the public link page"""
if not link_manager.page_exists(page_id):
return "Page not found", 404
link_manager.increment_view_count(page_id)
page_data = link_manager.get_page(page_id)
return render_template('link_page.html', page=page_data)
@bp.route('/edit/<page_id>')
@login_required
def edit_link_page(page_id):
"""Display the edit interface for the link page"""
if not link_manager.page_exists(page_id):
return "Page not found", 404
page_data = link_manager.get_page(page_id)
return render_template('edit_links.html', page=page_data)
@bp.route('/health')
def health_check():
"""Health check endpoint for Docker"""
from datetime import datetime
from flask import jsonify
return jsonify({'status': 'healthy', 'timestamp': datetime.now().isoformat()})

View File

@@ -305,6 +305,11 @@
<div class="header">
<h1>🎯 QR Code Manager</h1>
<p>Create, customize, and manage your QR codes with ease</p>
<div style="position: absolute; top: 20px; right: 20px;">
<a href="/logout" style="color: white; text-decoration: none; background: rgba(255,255,255,0.2); padding: 8px 15px; border-radius: 20px; font-size: 0.9em;">
👤 Logout
</a>
</div>
</div>
<div class="main-content">

253
app/templates/login.html Normal file
View File

@@ -0,0 +1,253 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>QR Code Manager - Login</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.login-container {
background: white;
border-radius: 15px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
overflow: hidden;
width: 100%;
max-width: 400px;
}
.login-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 40px 30px;
text-align: center;
}
.login-header h1 {
font-size: 2.5em;
margin-bottom: 10px;
font-weight: 300;
}
.login-header p {
font-size: 1.1em;
opacity: 0.9;
}
.login-form {
padding: 40px 30px;
}
.form-group {
margin-bottom: 25px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #555;
}
.form-group input {
width: 100%;
padding: 15px;
border: 2px solid #e9ecef;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.3s;
background: #f8f9fa;
}
.form-group input:focus {
outline: none;
border-color: #667eea;
background: white;
}
.login-btn {
width: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px;
border: none;
border-radius: 8px;
font-size: 18px;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s;
}
.login-btn:hover {
transform: translateY(-2px);
}
.login-btn:active {
transform: translateY(0);
}
.alert {
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
text-align: center;
font-weight: 500;
}
.alert-error {
background: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
}
.alert-success {
background: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
}
.alert-info {
background: #d1ecf1;
border: 1px solid #bee5eb;
color: #0c5460;
}
.login-footer {
padding: 20px 30px;
background: #f8f9fa;
text-align: center;
color: #666;
font-size: 0.9em;
}
.default-credentials {
background: #fff3cd;
border: 1px solid #ffeaa7;
color: #856404;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
text-align: center;
font-size: 0.9em;
}
.default-credentials strong {
display: block;
margin-bottom: 5px;
}
@media (max-width: 480px) {
.login-container {
margin: 10px;
}
.login-header {
padding: 30px 20px;
}
.login-header h1 {
font-size: 2em;
}
.login-form {
padding: 30px 20px;
}
}
/* Security icon animation */
.security-icon {
font-size: 3em;
margin-bottom: 15px;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
</style>
</head>
<body>
<div class="login-container">
<div class="login-header">
<div class="security-icon">🔐</div>
<h1>QR Manager</h1>
<p>Secure Admin Access</p>
</div>
<div class="login-form">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="default-credentials">
<strong>Default Login Credentials:</strong>
Username: admin<br>
Password: admin123
</div>
<form method="POST">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" required autocomplete="username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required autocomplete="current-password">
</div>
<button type="submit" class="login-btn">🚀 Login</button>
</form>
</div>
<div class="login-footer">
<p>🔒 Secure QR Code Management System</p>
<p style="margin-top: 5px; font-size: 0.8em; opacity: 0.7;">
Change default credentials in production
</p>
</div>
</div>
<script>
// Auto-focus on username field
document.getElementById('username').focus();
// Handle form submission
document.querySelector('form').addEventListener('submit', function(e) {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
if (!username || !password) {
e.preventDefault();
alert('Please enter both username and password');
return;
}
// Show loading state
const btn = document.querySelector('.login-btn');
btn.innerHTML = '🔄 Logging in...';
btn.disabled = true;
});
</script>
</body>
</html>

20
app/utils/__init__.py Normal file
View File

@@ -0,0 +1,20 @@
"""
Utility modules for QR Code Manager
"""
from .auth import init_admin, login_required, verify_password, get_admin_credentials
from .qr_generator import QRCodeGenerator
from .link_manager import LinkPageManager, link_pages_db
from .data_manager import QRDataManager, qr_codes_db
__all__ = [
'init_admin',
'login_required',
'verify_password',
'get_admin_credentials',
'QRCodeGenerator',
'LinkPageManager',
'link_pages_db',
'QRDataManager',
'qr_codes_db'
]

39
app/utils/auth.py Normal file
View File

@@ -0,0 +1,39 @@
"""
Authentication utilities for QR Code Manager
"""
import os
import bcrypt
from functools import wraps
from flask import session, redirect, url_for, request, jsonify
# Admin configuration
ADMIN_USERNAME = os.environ.get('ADMIN_USERNAME', 'admin')
ADMIN_PASSWORD_HASH = None
def init_admin():
"""Initialize admin user with password from environment or default"""
global ADMIN_PASSWORD_HASH
admin_password = os.environ.get('ADMIN_PASSWORD', 'admin123')
ADMIN_PASSWORD_HASH = bcrypt.hashpw(admin_password.encode('utf-8'), bcrypt.gensalt())
print(f"Admin user initialized: {ADMIN_USERNAME}")
print(f"Default password: {admin_password if admin_password == 'admin123' else '***'}")
def verify_password(password, hashed):
"""Verify a password against its hash"""
return bcrypt.checkpw(password.encode('utf-8'), hashed)
def login_required(f):
"""Authentication decorator"""
@wraps(f)
def decorated_function(*args, **kwargs):
if 'logged_in' not in session:
if request.endpoint and request.endpoint.startswith('api'):
return jsonify({'error': 'Authentication required'}), 401
return redirect(url_for('auth.login'))
return f(*args, **kwargs)
return decorated_function
def get_admin_credentials():
"""Get admin credentials for authentication"""
return ADMIN_USERNAME, ADMIN_PASSWORD_HASH

58
app/utils/data_manager.py Normal file
View File

@@ -0,0 +1,58 @@
"""
Data storage utilities for QR codes
"""
import uuid
from datetime import datetime
# In-memory storage for QR codes (in production, use a database)
qr_codes_db = {}
class QRDataManager:
def __init__(self):
pass
def save_qr_record(self, qr_type, content, settings, image_data, page_id=None):
"""Save QR code record to database"""
qr_id = str(uuid.uuid4())
qr_record = {
'id': qr_id,
'type': qr_type,
'content': content,
'settings': settings,
'created_at': datetime.now().isoformat(),
'image_data': image_data
}
if page_id:
qr_record['page_id'] = page_id
qr_codes_db[qr_id] = qr_record
return qr_id
def get_qr_record(self, qr_id):
"""Get QR code record"""
return qr_codes_db.get(qr_id)
def delete_qr_record(self, qr_id):
"""Delete QR code record"""
if qr_id in qr_codes_db:
del qr_codes_db[qr_id]
return True
return False
def list_qr_codes(self):
"""List all QR codes"""
qr_list = []
for qr_id, qr_data in qr_codes_db.items():
qr_list.append({
'id': qr_id,
'type': qr_data['type'],
'created_at': qr_data['created_at'],
'preview': f'data:image/png;base64,{qr_data["image_data"]}'
})
return qr_list
def qr_exists(self, qr_id):
"""Check if QR code exists"""
return qr_id in qr_codes_db

86
app/utils/link_manager.py Normal file
View File

@@ -0,0 +1,86 @@
"""
Dynamic Link Page Manager utilities
"""
import uuid
from datetime import datetime
# In-memory storage for dynamic link pages (in production, use a database)
link_pages_db = {}
class LinkPageManager:
def __init__(self):
pass
def create_link_page(self, title="My Links", description="Collection of useful links"):
"""Create a new dynamic link page"""
page_id = str(uuid.uuid4())
page_data = {
'id': page_id,
'title': title,
'description': description,
'links': [],
'created_at': datetime.now().isoformat(),
'updated_at': datetime.now().isoformat(),
'view_count': 0
}
link_pages_db[page_id] = page_data
return page_id
def add_link(self, page_id, title, url, description=""):
"""Add a link to a page"""
if page_id not in link_pages_db:
return False
link_data = {
'id': str(uuid.uuid4()),
'title': title,
'url': url if url.startswith(('http://', 'https://')) else f'https://{url}',
'description': description,
'created_at': datetime.now().isoformat()
}
link_pages_db[page_id]['links'].append(link_data)
link_pages_db[page_id]['updated_at'] = datetime.now().isoformat()
return True
def update_link(self, page_id, link_id, title=None, url=None, description=None):
"""Update a specific link"""
if page_id not in link_pages_db:
return False
for link in link_pages_db[page_id]['links']:
if link['id'] == link_id:
if title is not None:
link['title'] = title
if url is not None:
link['url'] = url if url.startswith(('http://', 'https://')) else f'https://{url}'
if description is not None:
link['description'] = description
link_pages_db[page_id]['updated_at'] = datetime.now().isoformat()
return True
return False
def delete_link(self, page_id, link_id):
"""Delete a specific link"""
if page_id not in link_pages_db:
return False
links = link_pages_db[page_id]['links']
link_pages_db[page_id]['links'] = [link for link in links if link['id'] != link_id]
link_pages_db[page_id]['updated_at'] = datetime.now().isoformat()
return True
def increment_view_count(self, page_id):
"""Increment view count for a page"""
if page_id in link_pages_db:
link_pages_db[page_id]['view_count'] += 1
def get_page(self, page_id):
"""Get page data"""
return link_pages_db.get(page_id)
def page_exists(self, page_id):
"""Check if page exists"""
return page_id in link_pages_db

95
app/utils/qr_generator.py Normal file
View File

@@ -0,0 +1,95 @@
"""
QR Code generation utilities
"""
import os
import qrcode
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.moduledrawers import RoundedModuleDrawer, CircleModuleDrawer, SquareModuleDrawer
from PIL import Image
class QRCodeGenerator:
def __init__(self):
self.default_settings = {
'size': 10,
'border': 4,
'error_correction': qrcode.constants.ERROR_CORRECT_M,
'foreground_color': '#000000',
'background_color': '#FFFFFF',
'style': 'square'
}
def generate_qr_code(self, data, settings=None):
"""Generate QR code with custom settings"""
if settings is None:
settings = self.default_settings.copy()
else:
merged_settings = self.default_settings.copy()
merged_settings.update(settings)
settings = merged_settings
# Create QR code instance
qr = qrcode.QRCode(
version=1,
error_correction=settings['error_correction'],
box_size=settings['size'],
border=settings['border'],
)
qr.add_data(data)
qr.make(fit=True)
# For styled QR codes with custom module drawer
if settings['style'] != 'square':
# Choose module drawer based on style
module_drawer = None
if settings['style'] == 'rounded':
module_drawer = RoundedModuleDrawer()
elif settings['style'] == 'circle':
module_drawer = CircleModuleDrawer()
# Generate the styled image
img = qr.make_image(
image_factory=StyledPilImage,
module_drawer=module_drawer,
fill_color=settings['foreground_color'],
back_color=settings['background_color']
)
else:
# Generate standard image with custom colors
img = qr.make_image(
fill_color=settings['foreground_color'],
back_color=settings['background_color']
)
return img
def add_logo(self, qr_img, logo_path, logo_size_ratio=0.2):
"""Add logo to QR code"""
try:
logo = Image.open(logo_path)
# Calculate logo size
qr_width, qr_height = qr_img.size
logo_size = int(min(qr_width, qr_height) * logo_size_ratio)
# Resize logo
logo = logo.resize((logo_size, logo_size), Image.Resampling.LANCZOS)
# Create a white background for the logo
logo_bg = Image.new('RGB', (logo_size + 20, logo_size + 20), 'white')
logo_bg.paste(logo, (10, 10))
# Calculate position to center the logo
logo_pos = (
(qr_width - logo_bg.width) // 2,
(qr_height - logo_bg.height) // 2
)
# Paste logo onto QR code
qr_img.paste(logo_bg, logo_pos)
return qr_img
except Exception as e:
print(f"Error adding logo: {e}")
return qr_img

98
deploy.sh Executable file
View File

@@ -0,0 +1,98 @@
#!/bin/bash
# QR Code Manager - Docker Build and Test Script
set -e # Exit on any error
echo "🐳 QR Code Manager - Docker Build & Test"
echo "========================================"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${GREEN}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
# Check prerequisites
echo "🔍 Checking prerequisites..."
if ! command -v docker &> /dev/null; then
print_error "Docker is not installed"
exit 1
fi
if ! docker compose version &> /dev/null; then
print_error "Docker Compose is not available"
exit 1
fi
print_status "Docker and Docker Compose are available"
# Create environment file if it doesn't exist
if [ ! -f .env ]; then
print_warning "Creating .env file from template"
cp .env.example .env
print_warning "Please review and update .env file with secure credentials!"
fi
# Build the Docker image
echo "🏗️ Building Docker image..."
if docker compose build; then
print_status "Docker image built successfully"
else
print_error "Docker build failed"
exit 1
fi
# Start the application
echo "🚀 Starting application..."
if docker compose up -d; then
print_status "Application started successfully"
else
print_error "Failed to start application"
exit 1
fi
# Wait for application to be ready
echo "⏳ Waiting for application to be ready..."
sleep 10
# Check if application is responding
if curl -f http://localhost:5000/health > /dev/null 2>&1; then
print_status "Application is healthy and responding"
else
print_warning "Health check failed, checking logs..."
docker compose logs qr-manager
fi
# Show status
echo "📊 Application Status:"
docker compose ps
echo ""
echo "🎉 Deployment Complete!"
echo "========================================"
echo "Application URL: http://localhost:5000"
echo "Default Login:"
echo " Username: admin"
echo " Password: admin123"
echo ""
echo "Management Commands:"
echo " View logs: docker compose logs -f"
echo " Stop app: docker compose down"
echo " Restart: docker compose restart"
echo ""
print_warning "Remember to change default credentials in production!"

39
docker-compose.yml Normal file
View File

@@ -0,0 +1,39 @@
version: '3.8'
services:
qr-manager:
build: .
container_name: qr-code-manager
restart: unless-stopped
ports:
- "5000:5000"
environment:
- FLASK_ENV=production
- SECRET_KEY=${SECRET_KEY:-your-super-secret-key-change-me}
- ADMIN_USERNAME=${ADMIN_USERNAME:-admin}
- ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin123}
volumes:
- qr_data:/app/app/static/qr_codes
- logo_data:/app/app/static/logos
- session_data:/app/flask_session
healthcheck:
test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:5000/health')"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
labels:
- "com.example.description=QR Code Manager Application"
- "com.example.service=qr-manager"
volumes:
qr_data:
driver: local
logo_data:
driver: local
session_data:
driver: local
networks:
default:
name: qr-manager-network

Binary file not shown.

Binary file not shown.

33
main.py Normal file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env python3
"""
QR Code Manager - Main Application Entry Point
A modern Flask web application for generating and managing QR codes with authentication.
Features include:
- Multiple QR code types (text, URL, WiFi, email, SMS, vCard)
- Dynamic link pages for managing collections of links
- Admin authentication with bcrypt password hashing
- Docker deployment ready
- Modern responsive web interface
"""
import os
from app import create_app
# Create Flask application
app = create_app()
if __name__ == '__main__':
# Production vs Development configuration
is_production = os.environ.get('FLASK_ENV') == 'production'
if is_production:
print("🚀 Starting QR Code Manager in PRODUCTION mode")
print("🔐 Admin user: admin")
print("🔒 Make sure to change default credentials!")
app.run(host='0.0.0.0', port=5000, debug=False)
else:
print("🛠️ Starting QR Code Manager in DEVELOPMENT mode")
print("🔐 Admin user: admin")
print("🔑 Default password: admin123")
app.run(host='0.0.0.0', port=5000, debug=True)

View File

@@ -4,3 +4,6 @@ Pillow==10.0.1
flask-cors==4.0.0
python-dotenv==1.0.0
requests==2.31.0
flask-session==0.5.0
werkzeug==2.3.7
bcrypt==4.0.1

View File

@@ -1,2 +0,0 @@
# This file ensures the directory is tracked by git
# Uploaded logo files will be stored here

View File

@@ -1,2 +0,0 @@
# This file ensures the directory is tracked by git
# Generated QR code images will be stored here

View File

@@ -1,208 +0,0 @@
#!/usr/bin/env python3
"""
Test script for QR Code Manager API
Run this script while the server is running to test API functionality
"""
import requests
import json
import base64
from PIL import Image
import io
# Server URL
BASE_URL = "http://localhost:5000"
def test_text_qr():
"""Test generating a text QR code"""
print("Testing text QR code generation...")
data = {
"type": "text",
"content": "Hello, World! This is a test QR code.",
"foreground_color": "#000000",
"background_color": "#FFFFFF",
"style": "square",
"size": 10
}
response = requests.post(f"{BASE_URL}/api/generate", json=data)
if response.status_code == 200:
result = response.json()
if result['success']:
print(f"✅ Text QR generated successfully! ID: {result['qr_id']}")
return result['qr_id']
else:
print(f"❌ Error: {result['error']}")
else:
print(f"❌ HTTP Error: {response.status_code}")
return None
def test_url_qr():
"""Test generating a URL QR code"""
print("Testing URL QR code generation...")
data = {
"type": "url",
"content": "https://github.com",
"foreground_color": "#0066cc",
"background_color": "#ffffff",
"style": "rounded",
"size": 12
}
response = requests.post(f"{BASE_URL}/api/generate", json=data)
if response.status_code == 200:
result = response.json()
if result['success']:
print(f"✅ URL QR generated successfully! ID: {result['qr_id']}")
return result['qr_id']
else:
print(f"❌ Error: {result['error']}")
else:
print(f"❌ HTTP Error: {response.status_code}")
return None
def test_wifi_qr():
"""Test generating a WiFi QR code"""
print("Testing WiFi QR code generation...")
data = {
"type": "wifi",
"wifi": {
"ssid": "TestNetwork",
"password": "TestPassword123",
"security": "WPA"
},
"foreground_color": "#ff6600",
"background_color": "#ffffff",
"style": "circle",
"size": 8
}
response = requests.post(f"{BASE_URL}/api/generate", json=data)
if response.status_code == 200:
result = response.json()
if result['success']:
print(f"✅ WiFi QR generated successfully! ID: {result['qr_id']}")
return result['qr_id']
else:
print(f"❌ Error: {result['error']}")
else:
print(f"❌ HTTP Error: {response.status_code}")
return None
def test_list_qr_codes():
"""Test listing all QR codes"""
print("Testing QR codes listing...")
response = requests.get(f"{BASE_URL}/api/qr_codes")
if response.status_code == 200:
qr_codes = response.json()
print(f"✅ Found {len(qr_codes)} QR codes")
for qr in qr_codes:
print(f" - {qr['id']}: {qr['type']} (created: {qr['created_at']})")
return qr_codes
else:
print(f"❌ HTTP Error: {response.status_code}")
return []
def test_download_qr(qr_id):
"""Test downloading a QR code"""
print(f"Testing QR code download for ID: {qr_id}")
response = requests.get(f"{BASE_URL}/api/download/{qr_id}")
if response.status_code == 200:
print(f"✅ QR code downloaded successfully! Size: {len(response.content)} bytes")
# Verify it's a valid PNG image
try:
image = Image.open(io.BytesIO(response.content))
print(f" Image format: {image.format}, Size: {image.size}")
except Exception as e:
print(f"❌ Invalid image data: {e}")
else:
print(f"❌ HTTP Error: {response.status_code}")
def test_get_qr_details(qr_id):
"""Test getting QR code details"""
print(f"Testing QR code details for ID: {qr_id}")
response = requests.get(f"{BASE_URL}/api/qr_codes/{qr_id}")
if response.status_code == 200:
qr_data = response.json()
print(f"✅ QR code details retrieved!")
print(f" Type: {qr_data['type']}")
print(f" Created: {qr_data['created_at']}")
print(f" Settings: {qr_data['settings']}")
else:
print(f"❌ HTTP Error: {response.status_code}")
def test_delete_qr(qr_id):
"""Test deleting a QR code"""
print(f"Testing QR code deletion for ID: {qr_id}")
response = requests.delete(f"{BASE_URL}/api/qr_codes/{qr_id}")
if response.status_code == 200:
result = response.json()
if result['success']:
print(f"✅ QR code deleted successfully!")
else:
print(f"❌ Error: {result['error']}")
else:
print(f"❌ HTTP Error: {response.status_code}")
def main():
"""Run all tests"""
print("🚀 Starting QR Code Manager API Tests")
print("=" * 50)
# Test generating different types of QR codes
text_qr_id = test_text_qr()
print()
url_qr_id = test_url_qr()
print()
wifi_qr_id = test_wifi_qr()
print()
# Test listing QR codes
qr_codes = test_list_qr_codes()
print()
# Test operations on generated QR codes
if text_qr_id:
test_download_qr(text_qr_id)
print()
test_get_qr_details(text_qr_id)
print()
# Test deletion (only delete one to keep some for manual testing)
if wifi_qr_id:
test_delete_qr(wifi_qr_id)
print()
print("🏁 Tests completed!")
print("You can now open http://localhost:5000 in your browser to test the web interface.")
if __name__ == "__main__":
try:
main()
except requests.exceptions.ConnectionError:
print("❌ Cannot connect to the server. Make sure the QR Code Manager is running on localhost:5000")
print("Start the server with: python app.py")
except Exception as e:
print(f"❌ Unexpected error: {e}")

View File

@@ -1,162 +0,0 @@
#!/usr/bin/env python3
"""
Test script for the new Dynamic Link Page feature
This will create a link page and demonstrate its functionality
"""
import requests
import json
import time
# Server URL
BASE_URL = "http://localhost:5000"
def test_create_link_page():
"""Test creating a dynamic link page"""
print("🚀 Testing Dynamic Link Page Creation...")
data = {
"title": "My Awesome Links",
"description": "A collection of my favorite resources and tools",
"foreground_color": "#1565c0",
"background_color": "#ffffff",
"style": "rounded",
"size": 12
}
response = requests.post(f"{BASE_URL}/api/create_link_page", json=data)
if response.status_code == 200:
result = response.json()
if result['success']:
print(f"✅ Link page created successfully!")
print(f"📄 Page ID: {result['page_id']}")
print(f"🔗 Public URL: {result['page_url']}")
print(f"✏️ Edit URL: {result['edit_url']}")
print(f"📱 QR ID: {result['qr_id']}")
return result
else:
print(f"❌ Error: {result['error']}")
else:
print(f"❌ HTTP Error: {response.status_code}")
return None
def test_add_links(page_id):
"""Test adding links to the page"""
print(f"\n📝 Adding links to page {page_id}...")
links_to_add = [
{
"title": "GitHub",
"url": "https://github.com",
"description": "The world's leading software development platform"
},
{
"title": "Stack Overflow",
"url": "https://stackoverflow.com",
"description": "Q&A platform for programmers"
},
{
"title": "MDN Web Docs",
"url": "https://developer.mozilla.org",
"description": "Complete web development documentation"
},
{
"title": "VS Code",
"url": "https://code.visualstudio.com",
"description": "Free source-code editor by Microsoft"
}
]
for link_data in links_to_add:
response = requests.post(f"{BASE_URL}/api/link_pages/{page_id}/links", json=link_data)
if response.status_code == 200:
result = response.json()
if result['success']:
print(f"✅ Added: {link_data['title']}")
else:
print(f"❌ Failed to add {link_data['title']}: {result['error']}")
else:
print(f"❌ HTTP Error adding {link_data['title']}: {response.status_code}")
def test_view_page(page_id):
"""Test viewing the page data"""
print(f"\n👀 Viewing page {page_id}...")
response = requests.get(f"{BASE_URL}/api/link_pages/{page_id}")
if response.status_code == 200:
page_data = response.json()
print(f"✅ Page loaded successfully!")
print(f" Title: {page_data['title']}")
print(f" Description: {page_data['description']}")
print(f" Links: {len(page_data['links'])}")
print(f" Views: {page_data['view_count']}")
for i, link in enumerate(page_data['links'], 1):
print(f" {i}. {link['title']}{link['url']}")
else:
print(f"❌ HTTP Error: {response.status_code}")
def test_update_link(page_id, link_id):
"""Test updating a link"""
print(f"\n✏️ Updating link {link_id}...")
update_data = {
"title": "GitHub (Updated)",
"description": "The world's leading software development platform - Now with Copilot!"
}
response = requests.put(f"{BASE_URL}/api/link_pages/{page_id}/links/{link_id}", json=update_data)
if response.status_code == 200:
result = response.json()
if result['success']:
print(f"✅ Link updated successfully!")
else:
print(f"❌ Failed to update link: {result['error']}")
else:
print(f"❌ HTTP Error: {response.status_code}")
def main():
"""Run the comprehensive test"""
print("🎯 Dynamic Link Page Feature Test")
print("=" * 50)
# Create a new link page
result = test_create_link_page()
if not result:
print("❌ Failed to create link page. Exiting.")
return
page_id = result['page_id']
page_url = result['page_url']
edit_url = result['edit_url']
# Add some links
test_add_links(page_id)
# View the page data
test_view_page(page_id)
print(f"\n🎉 Test completed successfully!")
print(f"📱 QR Code Points to: {page_url}")
print(f"✏️ Edit Interface: {edit_url}")
print(f"🌐 Public Page: {page_url}")
print("\nNow you can:")
print("1. Scan the QR code to visit the public page")
print("2. Open the edit URL to manage links")
print("3. Share the QR code - it will always point to the same page!")
print("4. Update links anytime without changing the QR code")
if __name__ == "__main__":
try:
main()
except requests.exceptions.ConnectionError:
print("❌ Cannot connect to the server. Make sure the QR Code Manager is running on localhost:5000")
print("Start the server with: python app.py")
except Exception as e:
print(f"❌ Unexpected error: {e}")