first commit
This commit is contained in:
145
.gitignore
vendored
Normal file
145
.gitignore
vendored
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# Generated QR codes and uploads
|
||||||
|
static/qr_codes/*.png
|
||||||
|
static/logos/*
|
||||||
|
!static/qr_codes/.gitkeep
|
||||||
|
!static/logos/.gitkeep
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
189
README.md
Normal file
189
README.md
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
## 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"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## File 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)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- **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
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- 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
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
### Styling
|
||||||
|
The application uses embedded CSS for easy customization. Modify the `<style>` section in `templates/index.html` to change the appearance.
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is open source. Feel free to modify and distribute according to your needs.
|
||||||
286
app.py
Normal file
286
app.py
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
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 qrcode.image.styles.colorfills import SolidFillColorMask
|
||||||
|
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 = {}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Choose module drawer based on style
|
||||||
|
module_drawer = None
|
||||||
|
if settings['style'] == 'rounded':
|
||||||
|
module_drawer = RoundedModuleDrawer()
|
||||||
|
elif settings['style'] == 'circle':
|
||||||
|
module_drawer = CircleModuleDrawer()
|
||||||
|
else:
|
||||||
|
module_drawer = SquareModuleDrawer()
|
||||||
|
|
||||||
|
# Create color mask
|
||||||
|
color_mask = SolidFillColorMask(
|
||||||
|
back_color=settings['background_color'],
|
||||||
|
front_color=settings['foreground_color']
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate the image
|
||||||
|
img = qr.make_image(
|
||||||
|
image_factory=StyledPilImage,
|
||||||
|
module_drawer=module_drawer,
|
||||||
|
color_mask=color_mask
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Initialize QR code generator
|
||||||
|
qr_generator = QRCodeGenerator()
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||||
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Flask==2.3.3
|
||||||
|
qrcode[pil]==7.4.2
|
||||||
|
Pillow==10.0.1
|
||||||
|
flask-cors==4.0.0
|
||||||
|
python-dotenv==1.0.0
|
||||||
2
static/logos/.gitkeep
Normal file
2
static/logos/.gitkeep
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# This file ensures the directory is tracked by git
|
||||||
|
# Uploaded logo files will be stored here
|
||||||
2
static/qr_codes/.gitkeep
Normal file
2
static/qr_codes/.gitkeep
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# This file ensures the directory is tracked by git
|
||||||
|
# Generated QR code images will be stored here
|
||||||
668
templates/index.html
Normal file
668
templates/index.html
Normal file
@@ -0,0 +1,668 @@
|
|||||||
|
<!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</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;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 2.5em;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header p {
|
||||||
|
font-size: 1.1em;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 30px;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 25px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section h2 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input,
|
||||||
|
.form-group select,
|
||||||
|
.form-group textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus,
|
||||||
|
.form-group select:focus,
|
||||||
|
.form-group textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group textarea {
|
||||||
|
height: 100px;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-inputs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-input-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-input-group input[type="color"] {
|
||||||
|
width: 50px;
|
||||||
|
height: 40px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.style-selector {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.style-option {
|
||||||
|
padding: 10px;
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.style-option:hover {
|
||||||
|
border-color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.style-option.active {
|
||||||
|
border-color: #667eea;
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wifi-fields,
|
||||||
|
.email-fields,
|
||||||
|
.sms-fields,
|
||||||
|
.vcard-fields {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wifi-fields.active,
|
||||||
|
.email-fields.active,
|
||||||
|
.sms-fields.active,
|
||||||
|
.vcard-fields.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 12px 30px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s;
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-section {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-preview {
|
||||||
|
background: white;
|
||||||
|
border: 2px dashed #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 30px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
min-height: 300px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-preview img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 250px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-preview .placeholder {
|
||||||
|
color: #999;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-section {
|
||||||
|
display: none;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.download-section.active {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: #6c757d;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #28a745;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-history {
|
||||||
|
margin-top: 30px;
|
||||||
|
padding: 25px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-history h3 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
padding: 15px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-item img {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-item-info {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-item-info h4 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-item-info p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.qr-item-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-small {
|
||||||
|
padding: 5px 15px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.main-content {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-inputs {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.style-selector {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="header">
|
||||||
|
<h1>🎯 QR Code Manager</h1>
|
||||||
|
<p>Create, customize, and manage your QR codes with ease</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
<div class="form-section">
|
||||||
|
<h2>Generate QR Code</h2>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="qr-type">QR Code Type</label>
|
||||||
|
<select id="qr-type" onchange="toggleFields()">
|
||||||
|
<option value="text">Text</option>
|
||||||
|
<option value="url">URL/Website</option>
|
||||||
|
<option value="wifi">WiFi</option>
|
||||||
|
<option value="email">Email</option>
|
||||||
|
<option value="phone">Phone</option>
|
||||||
|
<option value="sms">SMS</option>
|
||||||
|
<option value="vcard">Contact Card</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Text/URL content -->
|
||||||
|
<div class="form-group" id="text-field">
|
||||||
|
<label for="content">Content</label>
|
||||||
|
<textarea id="content" placeholder="Enter your text or URL..."></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- WiFi fields -->
|
||||||
|
<div class="wifi-fields" id="wifi-fields">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="wifi-ssid">Network Name (SSID)</label>
|
||||||
|
<input type="text" id="wifi-ssid" placeholder="My WiFi Network">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="wifi-password">Password</label>
|
||||||
|
<input type="password" id="wifi-password" placeholder="WiFi Password">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="wifi-security">Security Type</label>
|
||||||
|
<select id="wifi-security">
|
||||||
|
<option value="WPA">WPA/WPA2</option>
|
||||||
|
<option value="WEP">WEP</option>
|
||||||
|
<option value="nopass">No Password</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Email fields -->
|
||||||
|
<div class="email-fields" id="email-fields">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email-address">Email Address</label>
|
||||||
|
<input type="email" id="email-address" placeholder="contact@example.com">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email-subject">Subject</label>
|
||||||
|
<input type="text" id="email-subject" placeholder="Email subject">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email-body">Message</label>
|
||||||
|
<textarea id="email-body" placeholder="Email message..."></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SMS fields -->
|
||||||
|
<div class="sms-fields" id="sms-fields">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sms-phone">Phone Number</label>
|
||||||
|
<input type="tel" id="sms-phone" placeholder="+1234567890">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sms-message">Message</label>
|
||||||
|
<textarea id="sms-message" placeholder="SMS message..."></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- vCard fields -->
|
||||||
|
<div class="vcard-fields" id="vcard-fields">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="vcard-name">Full Name</label>
|
||||||
|
<input type="text" id="vcard-name" placeholder="John Doe">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="vcard-organization">Organization</label>
|
||||||
|
<input type="text" id="vcard-organization" placeholder="Company Name">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="vcard-phone">Phone</label>
|
||||||
|
<input type="tel" id="vcard-phone" placeholder="+1234567890">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="vcard-email">Email</label>
|
||||||
|
<input type="email" id="vcard-email" placeholder="john@example.com">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="vcard-website">Website</label>
|
||||||
|
<input type="url" id="vcard-website" placeholder="https://example.com">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 style="margin: 25px 0 15px 0; color: #333;">Customization</h3>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Colors</label>
|
||||||
|
<div class="color-inputs">
|
||||||
|
<div class="color-input-group">
|
||||||
|
<input type="color" id="foreground-color" value="#000000">
|
||||||
|
<span>Foreground</span>
|
||||||
|
</div>
|
||||||
|
<div class="color-input-group">
|
||||||
|
<input type="color" id="background-color" value="#FFFFFF">
|
||||||
|
<span>Background</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Style</label>
|
||||||
|
<div class="style-selector">
|
||||||
|
<div class="style-option active" data-style="square">
|
||||||
|
<div>⬛</div>
|
||||||
|
<div>Square</div>
|
||||||
|
</div>
|
||||||
|
<div class="style-option" data-style="rounded">
|
||||||
|
<div>⬜</div>
|
||||||
|
<div>Rounded</div>
|
||||||
|
</div>
|
||||||
|
<div class="style-option" data-style="circle">
|
||||||
|
<div>⚫</div>
|
||||||
|
<div>Circle</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="size">Size</label>
|
||||||
|
<input type="range" id="size" min="5" max="15" value="10" oninput="updateSizeLabel()">
|
||||||
|
<small id="size-label">10px per module</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn" onclick="generateQR()">Generate QR Code</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="result-section">
|
||||||
|
<h2>Preview</h2>
|
||||||
|
<div class="qr-preview" id="qr-preview">
|
||||||
|
<div class="placeholder">
|
||||||
|
<div style="font-size: 3em; margin-bottom: 10px;">📱</div>
|
||||||
|
<div>Your QR code will appear here</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="download-section" id="download-section">
|
||||||
|
<button class="btn btn-primary" onclick="downloadQR()">Download PNG</button>
|
||||||
|
<button class="btn btn-secondary" onclick="copyToClipboard()">Copy Image</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="qr-history">
|
||||||
|
<h3>Recent QR Codes</h3>
|
||||||
|
<div id="qr-history-list">
|
||||||
|
<p style="color: #666; text-align: center;">No QR codes generated yet</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let currentQRId = null;
|
||||||
|
let currentQRData = null;
|
||||||
|
|
||||||
|
function toggleFields() {
|
||||||
|
const type = document.getElementById('qr-type').value;
|
||||||
|
|
||||||
|
// Hide all specific fields
|
||||||
|
document.getElementById('text-field').style.display = 'none';
|
||||||
|
document.querySelectorAll('.wifi-fields, .email-fields, .sms-fields, .vcard-fields').forEach(el => {
|
||||||
|
el.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show relevant fields
|
||||||
|
if (type === 'text' || type === 'url' || type === 'phone') {
|
||||||
|
document.getElementById('text-field').style.display = 'block';
|
||||||
|
const contentField = document.getElementById('content');
|
||||||
|
if (type === 'url') {
|
||||||
|
contentField.placeholder = 'https://example.com';
|
||||||
|
} else if (type === 'phone') {
|
||||||
|
contentField.placeholder = '+1234567890';
|
||||||
|
} else {
|
||||||
|
contentField.placeholder = 'Enter your text...';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.getElementById(`${type}-fields`).classList.add('active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSizeLabel() {
|
||||||
|
const size = document.getElementById('size').value;
|
||||||
|
document.getElementById('size-label').textContent = `${size}px per module`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Style selector
|
||||||
|
document.querySelectorAll('.style-option').forEach(option => {
|
||||||
|
option.addEventListener('click', function() {
|
||||||
|
document.querySelectorAll('.style-option').forEach(opt => opt.classList.remove('active'));
|
||||||
|
this.classList.add('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function generateQR() {
|
||||||
|
const type = document.getElementById('qr-type').value;
|
||||||
|
let content = '';
|
||||||
|
let additionalData = {};
|
||||||
|
|
||||||
|
// Get content based on type
|
||||||
|
if (type === 'text' || type === 'url' || type === 'phone') {
|
||||||
|
content = document.getElementById('content').value;
|
||||||
|
} else if (type === 'wifi') {
|
||||||
|
additionalData.wifi = {
|
||||||
|
ssid: document.getElementById('wifi-ssid').value,
|
||||||
|
password: document.getElementById('wifi-password').value,
|
||||||
|
security: document.getElementById('wifi-security').value
|
||||||
|
};
|
||||||
|
} else if (type === 'email') {
|
||||||
|
additionalData.email = {
|
||||||
|
email: document.getElementById('email-address').value,
|
||||||
|
subject: document.getElementById('email-subject').value,
|
||||||
|
body: document.getElementById('email-body').value
|
||||||
|
};
|
||||||
|
} else if (type === 'sms') {
|
||||||
|
additionalData.sms = {
|
||||||
|
phone: document.getElementById('sms-phone').value,
|
||||||
|
message: document.getElementById('sms-message').value
|
||||||
|
};
|
||||||
|
} else if (type === 'vcard') {
|
||||||
|
additionalData.vcard = {
|
||||||
|
name: document.getElementById('vcard-name').value,
|
||||||
|
organization: document.getElementById('vcard-organization').value,
|
||||||
|
phone: document.getElementById('vcard-phone').value,
|
||||||
|
email: document.getElementById('vcard-email').value,
|
||||||
|
website: document.getElementById('vcard-website').value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestData = {
|
||||||
|
type: type,
|
||||||
|
content: content,
|
||||||
|
...additionalData,
|
||||||
|
size: parseInt(document.getElementById('size').value),
|
||||||
|
foreground_color: document.getElementById('foreground-color').value,
|
||||||
|
background_color: document.getElementById('background-color').value,
|
||||||
|
style: document.querySelector('.style-option.active').dataset.style
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/generate', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(requestData)
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
currentQRId = result.qr_id;
|
||||||
|
currentQRData = result.image_data;
|
||||||
|
|
||||||
|
// Show QR code
|
||||||
|
const preview = document.getElementById('qr-preview');
|
||||||
|
preview.innerHTML = `<img src="${result.image_data}" alt="Generated QR Code">`;
|
||||||
|
|
||||||
|
// Show download buttons
|
||||||
|
document.getElementById('download-section').classList.add('active');
|
||||||
|
|
||||||
|
// Refresh history
|
||||||
|
loadQRHistory();
|
||||||
|
} else {
|
||||||
|
alert('Error generating QR code: ' + result.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('Error: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadQR() {
|
||||||
|
if (currentQRId) {
|
||||||
|
window.open(`/api/download/${currentQRId}`, '_blank');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyToClipboard() {
|
||||||
|
if (currentQRData) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(currentQRData);
|
||||||
|
const blob = await response.blob();
|
||||||
|
await navigator.clipboard.write([
|
||||||
|
new ClipboardItem({ [blob.type]: blob })
|
||||||
|
]);
|
||||||
|
alert('QR code copied to clipboard!');
|
||||||
|
} catch (error) {
|
||||||
|
alert('Failed to copy to clipboard');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadQRHistory() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/qr_codes');
|
||||||
|
const qrCodes = await response.json();
|
||||||
|
|
||||||
|
const historyList = document.getElementById('qr-history-list');
|
||||||
|
|
||||||
|
if (qrCodes.length === 0) {
|
||||||
|
historyList.innerHTML = '<p style="color: #666; text-align: center;">No QR codes generated yet</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
historyList.innerHTML = qrCodes.map(qr => `
|
||||||
|
<div class="qr-item">
|
||||||
|
<img src="${qr.preview}" alt="QR Code">
|
||||||
|
<div class="qr-item-info">
|
||||||
|
<h4>${qr.type.toUpperCase()}</h4>
|
||||||
|
<p>Created: ${new Date(qr.created_at).toLocaleDateString()}</p>
|
||||||
|
</div>
|
||||||
|
<div class="qr-item-actions">
|
||||||
|
<button class="btn btn-small btn-primary" onclick="downloadQRById('${qr.id}')">Download</button>
|
||||||
|
<button class="btn btn-small btn-secondary" onclick="deleteQR('${qr.id}')">Delete</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load QR history:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function downloadQRById(qrId) {
|
||||||
|
window.open(`/api/download/${qrId}`, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteQR(qrId) {
|
||||||
|
if (confirm('Are you sure you want to delete this QR code?')) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/qr_codes/${qrId}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
loadQRHistory();
|
||||||
|
} else {
|
||||||
|
alert('Failed to delete QR code');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
alert('Error deleting QR code: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load history on page load
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
loadQRHistory();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
208
test_api.py
Normal file
208
test_api.py
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
#!/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}")
|
||||||
Reference in New Issue
Block a user