This commit is contained in:
Minecon724 2025-04-14 15:01:07 +00:00
commit f097a8fdb3
Signed by: Minecon724
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 werkzeug.middleware.proxy_fix import ProxyFix
from argon2 import PasswordHasher
# Load environment variables from .env file
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.config['SECRET_KEY'] = get('SECRET_KEY')
app.config['BEHIND_PROXY'] = get('BEHIND_PROXY', bool)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')
app.config['BEHIND_PROXY'] = os.environ.get('BEHIND_PROXY') == 'true'
app.url_map.strict_slashes = False
@ -72,10 +57,6 @@ register_blueprints(app)
from .scheduler import initialize_scheduler
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
@app.cli.command("run-migrations")
def run_migrations_command():

View file

@ -31,7 +31,7 @@ def index():
@login_required
@require_role(Role.ADMIN)
def dashboard():
return render_template('dashboard.html')
return render_template('admin_dashboard.html')
@blueprint.route('/manage_user/<id>', methods=['GET', 'POST'])
@login_required
@ -49,4 +49,4 @@ def manage_user(id: int):
form.email.data = user.email
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):
login_user(user)
return redirect_to_next(default=url_for('.login_success'))
return redirect_to_next()
else:
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'])
def register():
@ -92,15 +92,11 @@ def register():
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')
def register_success():
return render_template('register_success.html')
@blueprint.route('/login_success')
def login_success():
return redirect_to_next()
return render_template('auth_register_success.html')
@blueprint.route('/logout')
def logout():

View file

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

View file

@ -7,8 +7,38 @@
{% endblock %}
{% block content %}
<p>ID: {{ resource.id }}</p>
<p>Name: {{ resource.name }}</p>
<p>Price: {{ resource.price }}</p>
<p>Publisher: {{ resource.user.username }}</p>
<div class="row">
<div class="col-sm-9">
<p>Lorem ipsum</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 %}

View file

@ -27,12 +27,12 @@ def create_database(app: Flask) -> SQLAlchemy:
def initialize_database(app: Flask, db: SQLAlchemy):
"""Create database tables and initialize default data."""
from .models import User
from .models import User, Resource
from .roles import Role
with app.app_context():
app.logger.info("Creating database tables...")
db.create_all() # Should now create all tables including Inquiry
db.create_all()
# Run migrations after creating tables
# 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
def user_loader(id):
return User.query.get(id)
# TODO find a better way to do this
app.jinja_env.globals['Role'] = Role
return login_manager

View file

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

View file

@ -1,6 +1,6 @@
{% from 'bootstrap5/form.html' import render_form %}
{% 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>
<html lang="en">
@ -23,7 +23,9 @@
{% if current_user.is_authenticated %}
{% if current_user.role >= Role.ADMIN %}
{{ 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') }}
{% endif %}
{% endif %}
@ -32,17 +34,17 @@
{% if current_user.is_authenticated %}
<div class="dropdown">
<a class="btn btn-dark dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ current_user.username }}
{{ current_user.role }}
</a>
<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>
<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>
</div>
{% 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 %}
</div>
</div>