final stage of the app
This commit is contained in:
20
app/utils/__init__.py
Normal file
20
app/utils/__init__.py
Normal 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
39
app/utils/auth.py
Normal 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
58
app/utils/data_manager.py
Normal 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
86
app/utils/link_manager.py
Normal 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
95
app/utils/qr_generator.py
Normal 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
|
||||
Reference in New Issue
Block a user