Skip to content

Commit f10cc68

Browse files
committed
updated rate limiting
1 parent 8907f19 commit f10cc68

File tree

13 files changed

+621
-96
lines changed

13 files changed

+621
-96
lines changed

.env.sample

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
# Required for Flask sessions and user authentication
2-
SECRET_KEY=your_secret_key_here
2+
SECRET_KEY=sketchmaker_secret_key_2024_secure_random_string
3+
4+
# Database Configuration
5+
DB_PATH=sqlite:///sketchmaker.db
6+
7+
# Rate Limiting Configuration
8+
RATE_LIMIT_PER_MINUTE=20
9+
RATE_LIMIT_PER_HOUR=200
10+
RATE_LIMIT_PER_DAY=1000
11+
RATE_LIMIT_STORAGE=memory
312

413
# Optional default values for new installations
514
# These can be changed through the settings interface
615
DEFAULT_PROVIDER=OpenAI # Options: OpenAI, Anthropic, Google Gemini
7-
DEFAULT_MODEL=gpt-4o # Should match a model name from the selected provider
16+
DEFAULT_MODEL=gpt-4o-mini # Should match a model name from the selected provider
17+
818

919
# Optional: Set to 'True' to enable debug mode
1020
# FLASK_DEBUG=False

app.py

Lines changed: 116 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
1-
from flask import Flask, render_template
2-
from flask_login import LoginManager
3-
from models import db, User, APIProvider, AIModel
4-
from blueprints.auth import auth_bp
5-
from blueprints.core import core_bp
6-
from blueprints.generate import generate_bp
7-
from blueprints.gallery import gallery_bp
8-
from blueprints.download import download_bp
9-
from blueprints.admin import admin
1+
from flask import Flask, render_template, jsonify, request
2+
from extensions import db, login_manager, limiter, get_rate_limit_string
103
import os
114
from dotenv import load_dotenv
125

@@ -18,7 +11,7 @@ def create_app():
1811

1912
# Configuration
2013
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')
21-
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///sketchmaker.db'
14+
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DB_PATH', 'sqlite:///sketchmaker.db')
2215
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
2316

2417
# Ensure required environment variables are set
@@ -29,94 +22,127 @@ def create_app():
2922

3023
# Initialize extensions
3124
db.init_app(app)
32-
login_manager = LoginManager()
33-
login_manager.login_view = 'auth.login'
25+
limiter.init_app(app)
3426
login_manager.init_app(app)
35-
36-
# Create database tables and initialize providers
37-
with app.app_context():
38-
db.create_all()
39-
init_api_providers(app)
40-
41-
# Register blueprints
42-
app.register_blueprint(auth_bp)
43-
app.register_blueprint(core_bp)
44-
app.register_blueprint(generate_bp)
45-
app.register_blueprint(gallery_bp)
46-
app.register_blueprint(download_bp)
47-
app.register_blueprint(admin)
48-
49-
# Error handlers
50-
@app.errorhandler(404)
51-
def page_not_found(e):
52-
return render_template('404.html'), 404
53-
54-
# User loader
55-
@login_manager.user_loader
56-
def load_user(user_id):
57-
return db.session.get(User, int(user_id))
58-
59-
return app
6027

61-
def init_api_providers(app):
62-
"""Initialize default API providers and models"""
6328
with app.app_context():
64-
# Only initialize if no providers exist
65-
if APIProvider.query.first() is None:
66-
# Create providers
67-
openai = APIProvider(name="OpenAI")
68-
anthropic = APIProvider(name="Anthropic")
69-
gemini = APIProvider(name="Google Gemini")
70-
groq = APIProvider(name="Groq")
71-
db.session.add_all([openai, anthropic, gemini, groq])
72-
db.session.commit()
29+
# Import models here to avoid circular imports
30+
from models import User, APIProvider, AIModel
31+
32+
# Import blueprints
33+
from blueprints.auth import auth_bp
34+
from blueprints.core import core_bp
35+
from blueprints.generate import generate_bp
36+
from blueprints.gallery import gallery_bp
37+
from blueprints.download import download_bp
38+
from blueprints.admin import admin
39+
40+
# Register blueprints
41+
app.register_blueprint(auth_bp)
42+
app.register_blueprint(core_bp)
43+
app.register_blueprint(generate_bp)
44+
app.register_blueprint(gallery_bp)
45+
app.register_blueprint(download_bp)
46+
app.register_blueprint(admin)
47+
48+
# Error handlers
49+
@app.errorhandler(404)
50+
def page_not_found(e):
51+
return render_template('errors/404.html'), 404
7352

74-
# OpenAI models
75-
openai_models = [
76-
"gpt-4o",
77-
"gpt-4o-mini",
78-
"o1-preview",
79-
"o1-mini"
80-
]
81-
for model in openai_models:
82-
db.session.add(AIModel(name=model, provider_id=openai.id))
53+
@app.errorhandler(429)
54+
def ratelimit_handler(e):
55+
# Default retry time of 60 seconds if not provided
56+
retry_after = getattr(e, 'retry_after', 60)
57+
if retry_after is None:
58+
retry_after = 60
8359

84-
# Anthropic models
85-
anthropic_models = [
86-
"claude-3-5-sonnet-20241022",
87-
"claude-3-5-haiku-20241022",
88-
"claude-3-opus-20240229",
89-
"claude-3-haiku-20240307"
90-
]
91-
for model in anthropic_models:
92-
db.session.add(AIModel(name=model, provider_id=anthropic.id))
60+
# For API endpoints, return JSON response
61+
if request.path.startswith('/api/'):
62+
return jsonify({
63+
"error": "Rate limit exceeded",
64+
"message": "Too many requests. Please try again later.",
65+
"retry_after": retry_after
66+
}), 429
67+
68+
# For web pages, render template
69+
return render_template('errors/429.html',
70+
retry_after=retry_after
71+
), 429
9372

94-
# Google Gemini models
95-
gemini_models = [
96-
"gemini-1.5-flash-002",
97-
"gemini-1.5-flash-exp-0827",
98-
"gemini-1.5-flash-8b-exp-0827",
99-
"gemini-1.5-pro-002",
100-
"gemini-1.5-pro-exp-0827"
101-
]
102-
for model in gemini_models:
103-
db.session.add(AIModel(name=model, provider_id=gemini.id))
73+
@app.errorhandler(500)
74+
def internal_server_error(e):
75+
return render_template('errors/500.html'), 500
76+
77+
# User loader
78+
@login_manager.user_loader
79+
def load_user(user_id):
80+
return db.session.get(User, int(user_id))
10481

105-
# Groq models
106-
groq_models = [
107-
"llama-3.1-70b-versatile",
108-
"llama-3.1-8b-instant",
109-
"llama-3.2-11b-text-preview",
110-
"llama-3.2-11b-vision-preview",
111-
"llama-3.2-1b-preview",
112-
"llama-3.2-3b-preview",
113-
"llama-3.2-90b-text-preview",
114-
"llama-3.2-90b-vision-preview"
115-
]
116-
for model in groq_models:
117-
db.session.add(AIModel(name=model, provider_id=groq.id))
82+
def init_api_providers():
83+
"""Initialize default API providers and models"""
84+
# Only initialize if no providers exist
85+
if APIProvider.query.first() is None:
86+
# Create providers
87+
openai = APIProvider(name="OpenAI")
88+
anthropic = APIProvider(name="Anthropic")
89+
gemini = APIProvider(name="Google Gemini")
90+
groq = APIProvider(name="Groq")
91+
db.session.add_all([openai, anthropic, gemini, groq])
92+
db.session.commit()
11893

119-
db.session.commit()
94+
# OpenAI models
95+
openai_models = [
96+
"gpt-4o",
97+
"gpt-4o-mini",
98+
"o1-preview",
99+
"o1-mini"
100+
]
101+
for model in openai_models:
102+
db.session.add(AIModel(name=model, provider_id=openai.id))
103+
104+
# Anthropic models
105+
anthropic_models = [
106+
"claude-3-5-sonnet-20241022",
107+
"claude-3-5-haiku-20241022",
108+
"claude-3-opus-20240229",
109+
"claude-3-haiku-20240307"
110+
]
111+
for model in anthropic_models:
112+
db.session.add(AIModel(name=model, provider_id=anthropic.id))
113+
114+
# Google Gemini models
115+
gemini_models = [
116+
"gemini-1.5-flash-002",
117+
"gemini-1.5-flash-exp-0827",
118+
"gemini-1.5-flash-8b-exp-0827",
119+
"gemini-1.5-pro-002",
120+
"gemini-1.5-pro-exp-0827"
121+
]
122+
for model in gemini_models:
123+
db.session.add(AIModel(name=model, provider_id=gemini.id))
124+
125+
# Groq models
126+
groq_models = [
127+
"llama-3.1-70b-versatile",
128+
"llama-3.1-8b-instant",
129+
"llama-3.2-11b-text-preview",
130+
"llama-3.2-11b-vision-preview",
131+
"llama-3.2-1b-preview",
132+
"llama-3.2-3b-preview",
133+
"llama-3.2-90b-text-preview",
134+
"llama-3.2-90b-vision-preview"
135+
]
136+
for model in groq_models:
137+
db.session.add(AIModel(name=model, provider_id=groq.id))
138+
139+
db.session.commit()
140+
141+
# Create database tables and initialize providers
142+
db.create_all()
143+
init_api_providers()
144+
145+
return app
120146

121147
# Create the application instance for gunicorn
122148
app = create_app()

blueprints/admin.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from flask_login import login_required, current_user
33
from functools import wraps
44
from models import db, User
5+
from extensions import limiter, get_rate_limit_string
56

67
admin = Blueprint('admin', __name__)
78

@@ -15,13 +16,15 @@ def decorated_function(*args, **kwargs):
1516
return decorated_function
1617

1718
@admin.route('/manage')
19+
@limiter.limit(get_rate_limit_string())
1820
@login_required
1921
@admin_required
2022
def manage():
2123
users = User.query.all()
2224
return render_template('admin/manage.html', users=users)
2325

2426
@admin.route('/manage/user/<int:user_id>', methods=['POST'])
27+
@limiter.limit(get_rate_limit_string())
2528
@login_required
2629
@admin_required
2730
def update_user(user_id):

blueprints/auth.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
from flask_login import login_user, logout_user, login_required, current_user
33
from werkzeug.security import generate_password_hash, check_password_hash
44
from models import db, User
5+
from extensions import limiter, get_rate_limit_string
56

67
auth_bp = Blueprint('auth', __name__)
78

89
@auth_bp.route('/login', methods=['GET', 'POST'])
10+
@limiter.limit(get_rate_limit_string())
911
def login():
1012
# Redirect to dashboard if user is already logged in
1113
if current_user.is_authenticated:
@@ -37,6 +39,7 @@ def login():
3739
return render_template('auth/login.html')
3840

3941
@auth_bp.route('/register', methods=['GET', 'POST'])
42+
@limiter.limit(get_rate_limit_string())
4043
def register():
4144
# Redirect to dashboard if user is already logged in
4245
if current_user.is_authenticated:
@@ -78,6 +81,7 @@ def register():
7881
return render_template('auth/register.html')
7982

8083
@auth_bp.route('/logout')
84+
@limiter.limit(get_rate_limit_string())
8185
@login_required
8286
def logout():
8387
logout_user()

blueprints/core.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,23 @@
22
from flask_login import login_required, current_user
33
from models import db, User, APIProvider, AIModel
44
from datetime import datetime
5+
from extensions import limiter, get_rate_limit_string
56

67
core_bp = Blueprint('core', __name__)
78

89
@core_bp.route('/')
10+
@limiter.limit(get_rate_limit_string())
911
def index():
1012
return render_template('index.html', current_year=datetime.now().year)
1113

1214
@core_bp.route('/dashboard')
15+
@limiter.limit(get_rate_limit_string())
1316
@login_required
1417
def dashboard():
1518
return render_template('dashboard.html')
1619

1720
@core_bp.route('/settings')
21+
@limiter.limit(get_rate_limit_string())
1822
@login_required
1923
def settings():
2024
# Get all providers and models
@@ -26,6 +30,7 @@ def settings():
2630
models=models)
2731

2832
@core_bp.route('/settings/update', methods=['POST'])
33+
@limiter.limit(get_rate_limit_string())
2934
@login_required
3035
def update_settings():
3136
try:
@@ -72,6 +77,7 @@ def update_settings():
7277
return jsonify({'error': str(e)}), 500
7378

7479
@core_bp.route('/settings/models/<int:provider_id>')
80+
@limiter.limit(get_rate_limit_string())
7581
@login_required
7682
def get_provider_models(provider_id):
7783
try:

blueprints/download.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from flask import Blueprint, send_file, abort
22
from flask_login import login_required, current_user
33
from models import Image
4+
from extensions import limiter, get_rate_limit_string
45
import os
56

67
download_bp = Blueprint('download', __name__)
78

89
@download_bp.route('/download/<filename>/<format>')
10+
@limiter.limit(get_rate_limit_string())
911
@login_required
1012
def download_image(filename, format):
1113
# Get base filename without extension

blueprints/gallery.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from flask import Blueprint, render_template, abort
22
from flask_login import login_required, current_user
33
from models import Image
4+
from extensions import limiter, get_rate_limit_string
45
import markdown2
56

67
gallery_bp = Blueprint('gallery', __name__)
78

89
@gallery_bp.route('/gallery')
10+
@limiter.limit(get_rate_limit_string())
911
@login_required
1012
def view_gallery():
1113
# Get user's images ordered by creation date (newest first)
@@ -16,6 +18,7 @@ def view_gallery():
1618
return render_template('gallery.html', images=images)
1719

1820
@gallery_bp.route('/gallery/<int:image_id>')
21+
@limiter.limit(get_rate_limit_string())
1922
@login_required
2023
def view_image(image_id):
2124
# Get the specific image and verify ownership

0 commit comments

Comments
 (0)