Update
This commit is contained in:
parent
c5e2e70332
commit
f097a8fdb3
18 changed files with 114 additions and 89 deletions
|
|
@ -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():
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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():
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 %}
|
|
||||||
|
|
@ -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 %}
|
||||||
|
|
@ -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'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 %}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue