This commit is contained in:
Minecon724 2025-04-14 15:01:07 +00:00
commit f097a8fdb3
Signed by untrusted user who does not match committer: m724
GPG key ID: A02E6E67AB961189
18 changed files with 114 additions and 89 deletions

View file

@ -5,28 +5,13 @@ from dotenv import load_dotenv
from flask import jsonify from flask import jsonify
from werkzeug.middleware.proxy_fix import ProxyFix from werkzeug.middleware.proxy_fix import ProxyFix
from argon2 import PasswordHasher from argon2 import PasswordHasher
# Load environment variables from .env file # Load environment variables from .env file
load_dotenv() load_dotenv()
DEFAULTS = {
'SECRET_KEY': '',
'BEHIND_PROXY': False
}
def get(key, type: type = str):
value = os.environ.get(key)
if value is None:
return DEFAULTS[key]
if type == bool:
value = value.lower() == 'true'
return value
app = Flask(__name__) app = Flask(__name__)
app.config['SECRET_KEY'] = get('SECRET_KEY') app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
app.config['BEHIND_PROXY'] = get('BEHIND_PROXY', bool) app.config['BEHIND_PROXY'] = os.environ.get('BEHIND_PROXY') == 'true'
app.url_map.strict_slashes = False app.url_map.strict_slashes = False
@ -72,10 +57,6 @@ register_blueprints(app)
from .scheduler import initialize_scheduler from .scheduler import initialize_scheduler
initialize_scheduler(app) initialize_scheduler(app)
# TODO find a better way to do this
from .roles import Role
app.jinja_env.globals['Role'] = Role
# Flask CLI command to run migrations # Flask CLI command to run migrations
@app.cli.command("run-migrations") @app.cli.command("run-migrations")
def run_migrations_command(): def run_migrations_command():

View file

@ -31,7 +31,7 @@ def index():
@login_required @login_required
@require_role(Role.ADMIN) @require_role(Role.ADMIN)
def dashboard(): def dashboard():
return render_template('dashboard.html') return render_template('admin_dashboard.html')
@blueprint.route('/manage_user/<id>', methods=['GET', 'POST']) @blueprint.route('/manage_user/<id>', methods=['GET', 'POST'])
@login_required @login_required
@ -49,4 +49,4 @@ def manage_user(id: int):
form.email.data = user.email form.email.data = user.email
form.username.data = user.username form.username.data = user.username
return render_template('manage_user.html', user=user) return render_template('admin_manage_user.html', user=user)

View file

@ -67,11 +67,11 @@ def login():
if user and user.verify_password(password): if user and user.verify_password(password):
login_user(user) login_user(user)
return redirect_to_next(default=url_for('.login_success')) return redirect_to_next()
else: else:
flash('Invalid username or password', 'error') flash('Invalid username or password', 'error')
return render_template('login.html', form=form) return render_template('auth_login.html', form=form)
@blueprint.route('/register', methods=['GET', 'POST']) @blueprint.route('/register', methods=['GET', 'POST'])
def register(): def register():
@ -92,15 +92,11 @@ def register():
return redirect(url_for('.register_success')) return redirect(url_for('.register_success'))
return render_template('register.html', form=form) return render_template('auth_register.html', form=form)
@blueprint.route('/register_success') @blueprint.route('/register_success')
def register_success(): def register_success():
return render_template('register_success.html') return render_template('auth_register_success.html')
@blueprint.route('/login_success')
def login_success():
return redirect_to_next()
@blueprint.route('/logout') @blueprint.route('/logout')
def logout(): def logout():

View file

@ -1,6 +1,7 @@
from flask import Blueprint, jsonify, Flask, request, render_template, redirect, url_for, flash from flask import Blueprint, jsonify, Flask, request, render_template, redirect, url_for, flash
from wtforms import Form, StringField, IntegerField, validators from wtforms import StringField, IntegerField, validators, SubmitField
from flask_login import login_required from flask_wtf import FlaskForm
from flask_login import login_required, current_user
from buybuilds.models.resource import Resource from buybuilds.models.resource import Resource
from buybuilds import db from buybuilds import db
@ -17,9 +18,11 @@ blueprint = Blueprint(
def register_routes(app: Flask): def register_routes(app: Flask):
app.register_blueprint(blueprint) app.register_blueprint(blueprint)
class CreateResourceForm(Form): class CreateResourceForm(FlaskForm):
name = StringField('Name', [validators.Length(min=2, max=30)]) name = StringField('Name', [validators.Length(min=2, max=30)])
price = IntegerField('Price', [validators.NumberRange(min=0, max=100)]) price = IntegerField('Price', [validators.NumberRange(min=0, max=100)])
description = StringField('Short description', [validators.Length(max=70)])
submit = SubmitField('Submit')
@blueprint.route('/', methods=['GET']) @blueprint.route('/', methods=['GET'])
def index(): def index():
@ -29,7 +32,7 @@ def index():
@login_required @login_required
@require_role(Role.PUBLISHER) @require_role(Role.PUBLISHER)
def dashboard(): def dashboard():
return render_template('dashboard.html') return render_template('publisher_dashboard.html')
@blueprint.route('/create_resource', methods=['GET', 'POST']) @blueprint.route('/create_resource', methods=['GET', 'POST'])
@login_required @login_required
@ -40,13 +43,17 @@ def create_resource():
if form.validate_on_submit(): if form.validate_on_submit():
name = form.name.data name = form.name.data
price = form.price.data price = form.price.data
description = form.description.data
resource = Resource(name=name, price=price * 100) resource = Resource.create(
db.session.add(resource) name=name,
db.session.commit() price=int(price * 100),
description=form.description.data,
user=current_user
)
flash("Resource created successfully", "success") flash("Resource created successfully", "success")
return redirect(url_for('resource.resource', resource_id=resource.id)) return redirect(url_for('resource.resource', resource_id=resource.id))
return render_template('create_resource.html', form=form) return render_template('publisher_create_resource.html', form=form)

View file

@ -1,34 +0,0 @@
{% extends 'base.html' %}
{% block title %}Publisher Dashboard{% endblock %}
{% block header %}
<h2>Publisher Dashboard</h2>
{% endblock %}
{% block content %}
<h3>All Resources</h3>
{% if resources %}
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{% for resource in resources %}
<tr>
<td>{{ resource.id }}</td>
<td>{{ resource.name }}</td>
<td>{{ resource.price }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>No resources found. <a href="{{ url_for('publisher.create_resource') }}">Create a new resource</a></p>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,33 @@
{% extends 'base.html' %}
{% block title %}Publisher Dashboard{% endblock %}
{% block header %}
<h2>Publisher Dashboard</h2>
{% endblock %}
{% block content %}
<h3>Your Resources</h3>
{% if current_user.resources %}
<div class="row">
{% for resource in current_user.resources %}
<div class="col-sm-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">{{ resource.name }}</h5>
<p class="card-text">
{{ resource.description }}
<br>
{{ render_icon('tag-fill') }} {{ resource.price / 100 }}$
</p>
<a href="{{ url_for('resource.resource', resource_id=resource.id) }}" class="btn btn-primary">View / Edit</a>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<p>No resources found. <a href="{{ url_for('publisher.create_resource') }}">Create a new resource</a></p>
{% endif %}
{% endblock %}

View file

@ -5,6 +5,7 @@ from buybuilds.models.resource import Resource
blueprint = Blueprint( blueprint = Blueprint(
name='resource', name='resource',
import_name=__name__, import_name=__name__,
template_folder='templates',
url_prefix='/resource' url_prefix='/resource'
) )

View file

@ -7,8 +7,38 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<p>ID: {{ resource.id }}</p> <div class="row">
<p>Name: {{ resource.name }}</p> <div class="col-sm-9">
<p>Price: {{ resource.price }}</p> <p>Lorem ipsum</p>
<p>Publisher: {{ resource.user.username }}</p> <p>Lorem ipsum</p>
<p>Lorem ipsum</p>
</div>
<div class="col-sm-3">
<div class="card">
<div class="card-body">
<h3 class="card-title">{{ resource.name }}</h3>
<figure class="card-text">
<blockquote>
<p>{{ resource.description }}</p>
</blockquote>
<figcaption class="blockquote-footer text-end">
<cite title="Source Title">{{ resource.user.username }}</cite>
</figcaption>
</figure>
</div>
<div class="card-footer">
<div class="row">
<div class="col">
<a class="btn btn-primary">Buy Now</a>
</div>
<div class="col d-flex align-items-center text-right">
<span>{{ render_icon('tags-fill') }} {{ resource.price }}$</span>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View file

@ -27,12 +27,12 @@ def create_database(app: Flask) -> SQLAlchemy:
def initialize_database(app: Flask, db: SQLAlchemy): def initialize_database(app: Flask, db: SQLAlchemy):
"""Create database tables and initialize default data.""" """Create database tables and initialize default data."""
from .models import User from .models import User, Resource
from .roles import Role from .roles import Role
with app.app_context(): with app.app_context():
app.logger.info("Creating database tables...") app.logger.info("Creating database tables...")
db.create_all() # Should now create all tables including Inquiry db.create_all()
# Run migrations after creating tables # Run migrations after creating tables
# This ensures any new columns added to existing tables are properly added # This ensures any new columns added to existing tables are properly added

View file

@ -14,6 +14,10 @@ def create_login_manager(app):
@login_manager.user_loader @login_manager.user_loader
def user_loader(id): def user_loader(id):
return User.query.get(id) return User.query.get(id)
# TODO find a better way to do this
app.jinja_env.globals['Role'] = Role
return login_manager return login_manager

View file

@ -3,15 +3,20 @@ from .user import User
class Resource(db.Model): class Resource(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), unique=True, nullable=False, index=True) name = db.Column(db.String(30), unique=True, nullable=False, index=True)
price = db.Column(db.Integer, nullable=False) price = db.Column(db.Integer, nullable=False)
description = db.Column(db.String(70))
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False, index=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False, index=True)
user = db.relationship('User', back_populates='resources') user = db.relationship('User', back_populates='resources')
@classmethod @classmethod
def create(cls, name: str, price: int, user: User) -> 'Resource': def create(cls, name: str, price: int, user: User, description: str = None) -> 'Resource':
resource = cls(name=name, price=price, user=user) resource = cls(name=name, price=price, description=description)
db.session.add(resource) user.resources.append(resource)
db.session.commit() db.session.commit()
return resource return resource

View file

@ -1,6 +1,6 @@
{% from 'bootstrap5/form.html' import render_form %} {% from 'bootstrap5/form.html' import render_form %}
{% from 'bootstrap5/nav.html' import render_nav_item %} {% from 'bootstrap5/nav.html' import render_nav_item %}
{% from 'bootstrap5/utils.html' import render_messages %} {% from 'bootstrap5/utils.html' import render_messages, render_icon %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@ -23,7 +23,9 @@
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
{% if current_user.role >= Role.ADMIN %} {% if current_user.role >= Role.ADMIN %}
{{ render_nav_item('admin.dashboard', 'Admin Dashboard') }} {{ render_nav_item('admin.dashboard', 'Admin Dashboard') }}
{% elif current_user.role >= Role.PUBLISHER %} {% endif %}
{% if current_user.role >= Role.PUBLISHER %}
{{ render_nav_item('publisher.dashboard', 'Publisher Dashboard') }} {{ render_nav_item('publisher.dashboard', 'Publisher Dashboard') }}
{% endif %} {% endif %}
{% endif %} {% endif %}
@ -32,17 +34,17 @@
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<div class="dropdown"> <div class="dropdown">
<a class="btn btn-dark dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false"> <a class="btn btn-dark dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ current_user.username }} {{ current_user.role }}
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li class="dropdown-item">[{{ current_user.role }}] {{ current_user.username }}</li> <li class="dropdown-item">{{ render_icon('person-fill') }} {{ current_user.username }}</li>
<li class="dropdown-divider"></li> <li class="dropdown-divider"></li>
<a class="dropdown-item" href="{{ url_for('auth.logout') }}">Logout</a> <a class="dropdown-item" href="{{ url_for('auth.logout') }}">{{ render_icon('door-open-fill') }} Logout</a>
</ul> </ul>
</div> </div>
{% else %} {% else %}
<a class="btn btn-success" href="{{ url_for('auth.login') }}">Login</a> <a class="btn btn-success" href="{{ url_for('auth.login') }}">{{ render_icon('door-open-fill') }} Login</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>