From 850360f5538ffefd3dec9aaac118bae674496998 Mon Sep 17 00:00:00 2001 From: Ske087 Date: Mon, 20 Jan 2025 15:42:10 +0200 Subject: [PATCH] added user registration and login --- __pycache__/app.cpython-311.pyc | Bin 0 -> 19538 bytes app.py | 130 ++++++++++++++++-- create_default_user.py | 20 +++ enviroment.txt | 2 + instance/dashboard.db | Bin 28672 -> 49152 bytes migrations/README | 1 + migrations/__pycache__/env.cpython-311.pyc | Bin 0 -> 5101 bytes migrations/alembic.ini | 50 +++++++ migrations/env.py | 113 +++++++++++++++ migrations/script.py.mako | 24 ++++ .../0de18b4ddaa3_initial_migration.py | 34 +++++ ...8b4ddaa3_initial_migration.cpython-311.pyc | Bin 0 -> 1812 bytes templates/admin.html | 68 +++++++++ templates/dashboard.html | 27 +++- templates/login.html | 24 ++++ templates/register.html | 24 ++++ 16 files changed, 500 insertions(+), 17 deletions(-) create mode 100644 __pycache__/app.cpython-311.pyc create mode 100644 create_default_user.py create mode 100644 migrations/README create mode 100644 migrations/__pycache__/env.cpython-311.pyc create mode 100644 migrations/alembic.ini create mode 100644 migrations/env.py create mode 100644 migrations/script.py.mako create mode 100644 migrations/versions/0de18b4ddaa3_initial_migration.py create mode 100644 migrations/versions/__pycache__/0de18b4ddaa3_initial_migration.cpython-311.pyc create mode 100644 templates/admin.html create mode 100644 templates/login.html create mode 100644 templates/register.html diff --git a/__pycache__/app.cpython-311.pyc b/__pycache__/app.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0c17199fc7c9a008d6538be2fa59b43718800ba1 GIT binary patch literal 19538 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 literal 0 HcmV?d00001 diff --git a/app.py b/app.py index 05a5d7e..76b6922 100644 --- a/app.py +++ b/app.py @@ -1,14 +1,22 @@ import os -from werkzeug.utils import secure_filename from flask import Flask, render_template, request, redirect, url_for from flask_sqlalchemy import SQLAlchemy +from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user +from flask_bcrypt import Bcrypt +from werkzeug.utils import secure_filename +from functools import wraps +from flask_migrate import Migrate app = Flask(__name__) +# Set the secret key to a fixed value +app.config['SECRET_KEY'] = 'Ana_Are_Multe_Mere-Si_Nu_Are_Pere' + # Configurare baza de date SQLite app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///dashboard.db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db = SQLAlchemy(app) +bcrypt = Bcrypt(app) UPLOAD_FOLDER = 'static/uploads' app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER @@ -17,13 +25,30 @@ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER if not os.path.exists(UPLOAD_FOLDER): os.makedirs(UPLOAD_FOLDER) +login_manager = LoginManager(app) +login_manager.login_view = 'login' + +migrate = Migrate(app, db) + + +@login_manager.user_loader +def load_user(user_id): + return User.query.get(int(user_id)) + # Modele pentru baza de date +class User(db.Model, UserMixin): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(80), unique=True, nullable=False) + password = db.Column(db.String(120), nullable=False) + role = db.Column(db.String(20), nullable=False, default='user') + class Player(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(80), nullable=False) hostname = db.Column(db.String(120), nullable=False) ip = db.Column(db.String(15), nullable=False) password = db.Column(db.String(120), nullable=False) + user_id = db.Column(db.Integer, db.ForeignKey('user.id', name='fk_user_id'), nullable=True) class Group(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -42,13 +67,52 @@ class Content(db.Model): player_id = db.Column(db.Integer, db.ForeignKey('player.id'), nullable=True) group_id = db.Column(db.Integer, db.ForeignKey('group.id'), nullable=True) +def admin_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if current_user.role != 'admin': + return redirect(url_for('dashboard')) + return f(*args, **kwargs) + return decorated_function + @app.route('/') +@login_required def dashboard(): players = Player.query.all() groups = Group.query.all() return render_template('dashboard.html', players=players, groups=groups) +@app.route('/register', methods=['GET', 'POST']) +def register(): + if request.method == 'POST': + username = request.form['username'] + password = request.form['password'] + hashed_password = bcrypt.generate_password_hash(password).decode('utf-8') + new_user = User(username=username, password=hashed_password, role='user') + db.session.add(new_user) + db.session.commit() + return redirect(url_for('login')) + return render_template('register.html') + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if request.method == 'POST': + username = request.form['username'] + password = request.form['password'] + user = User.query.filter_by(username=username).first() + if user and bcrypt.check_password_hash(user.password, password): + login_user(user) + return redirect(url_for('dashboard')) + return render_template('login.html') + +@app.route('/logout') +@login_required +def logout(): + logout_user() + return redirect(url_for('login')) + @app.route('/add_player', methods=['GET', 'POST']) +@login_required def add_player(): if request.method == 'POST': username = request.form['username'] @@ -65,6 +129,7 @@ def add_player(): return render_template('add_player.html') @app.route('/add_group', methods=['GET', 'POST']) +@login_required def add_group(): if request.method == 'POST': group_name = request.form['group_name'] @@ -84,6 +149,7 @@ def add_group(): return render_template('add_group.html', players=players) @app.route('/upload_content', methods=['GET', 'POST']) +@login_required def upload_content(): if request.method == 'POST': target_type = request.form['target_type'] @@ -92,22 +158,18 @@ def upload_content(): duration = int(request.form['duration']) for file in files: - if target_type == 'player': - new_content = Content(file_name=file.filename, duration=duration, player_id=int(target_id)) - elif target_type == 'group': - new_content = Content(file_name=file.filename, duration=duration, group_id=int(target_id)) + filename = secure_filename(file.filename) + file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename) + file.save(file_path) + new_content = Content(file_name=filename, duration=duration, player_id=target_id if target_type == 'player' else None, group_id=target_id if target_type == 'group' else None) db.session.add(new_content) db.session.commit() return redirect(url_for('dashboard')) - - players = Player.query.all() - groups = Group.query.all() - return render_template('upload_content.html', players=players, groups=groups) - -# ...existing code... + return render_template('upload_content.html') @app.route('/content//edit', methods=['POST']) +@login_required def edit_content(content_id): content = Content.query.get_or_404(content_id) new_duration = int(request.form['duration']) @@ -116,6 +178,7 @@ def edit_content(content_id): return redirect(url_for('player_page', player_id=content.player_id)) @app.route('/content//delete', methods=['POST']) +@login_required def delete_content(content_id): content = Content.query.get_or_404(content_id) player_id = content.player_id @@ -123,21 +186,22 @@ def delete_content(content_id): db.session.commit() return redirect(url_for('player_page', player_id=player_id)) -# ...existing code... - @app.route('/player//fullscreen') +@login_required def player_fullscreen(player_id): player = Player.query.get_or_404(player_id) content = Content.query.filter_by(player_id=player_id).all() return render_template('player_fullscreen.html', player=player, content=content) @app.route('/player/') +@login_required def player_page(player_id): player = Player.query.get_or_404(player_id) content = Content.query.filter_by(player_id=player_id).all() return render_template('player_page.html', player=player, content=content) @app.route('/player//upload', methods=['POST']) +@login_required def upload_content_to_player(player_id): player = Player.query.get_or_404(player_id) files = request.files.getlist('files') @@ -154,12 +218,52 @@ def upload_content_to_player(player_id): return redirect(url_for('player_page', player_id=player_id)) @app.route('/player//delete', methods=['POST']) +@login_required def delete_player(player_id): player = Player.query.get_or_404(player_id) db.session.delete(player) db.session.commit() return redirect(url_for('dashboard')) +@app.route('/admin') +@login_required +@admin_required +def admin(): + users = User.query.all() + return render_template('admin.html', users=users) + +@app.route('/admin/change_role/', methods=['POST']) +@login_required +@admin_required +def change_role(user_id): + user = User.query.get_or_404(user_id) + new_role = request.form['role'] + user.role = new_role + db.session.commit() + return redirect(url_for('admin')) + +@app.route('/admin/delete_user/', methods=['POST']) +@login_required +@admin_required +def delete_user(user_id): + user = User.query.get_or_404(user_id) + db.session.delete(user) + db.session.commit() + return redirect(url_for('admin')) + +@app.route('/admin/create_user', methods=['POST']) +@login_required +@admin_required +def create_user(): + username = request.form['username'] + password = request.form['password'] + role = request.form['role'] + hashed_password = bcrypt.generate_password_hash(password).decode('utf-8') + new_user = User(username=username, password=hashed_password, role=role) + db.session.add(new_user) + db.session.commit() + return redirect(url_for('admin')) + if __name__ == '__main__': with app.app_context(): db.create_all() # Creează toate tabelele diff --git a/create_default_user.py b/create_default_user.py new file mode 100644 index 0000000..d6b822a --- /dev/null +++ b/create_default_user.py @@ -0,0 +1,20 @@ +from app import app, db, User, bcrypt + +# Create the default user +username = 'admin' +password = '1234' +hashed_password = bcrypt.generate_password_hash(password).decode('utf-8') + +with app.app_context(): + # Delete the existing user if it exists + existing_user = User.query.filter_by(username=username).first() + if existing_user: + db.session.delete(existing_user) + db.session.commit() + + # Add the new user to the database + default_user = User(username=username, password=hashed_password, role='admin') + db.session.add(default_user) + db.session.commit() + +print(f"Default user '{username}' created with password '{password}'") \ No newline at end of file diff --git a/enviroment.txt b/enviroment.txt index ecbc84c..4a27a74 100644 --- a/enviroment.txt +++ b/enviroment.txt @@ -4,6 +4,8 @@ source digiscreen/bin/activate pip install flask sqlalchemy flask-sqlalchemy +pip install flask-login flask-bcrypt + python3 setup.py sdist python3 setup.py bdist_wheel flask \ No newline at end of file diff --git a/instance/dashboard.db b/instance/dashboard.db index 95211429f7939e0081343e54f138a49a936a2c57..4311077b67b96e87a61e633fa993170744360a81 100644 GIT binary patch delta 916 zcmZ9KQBT@X7=}+_6IzfnE?DEhc%~R3h_hgvWN}$Xsv}_0N_BYC6giAeC<@dJZUnMx zqv=2JA8h=9T`XC4v77A|Y{{~CW8$)lU3jy$ngwa{ot(bkd)_|ZNju+(PRD-Y_xb?< zZli2Q5k`@xv+*fEnA;^rVE|ChbKog^eveLjqOL2K!}W~{P@kRW&X4c9tP{cjkvGnm zkFn)X+51e7aXIL*G2-oVQ!Z5SenoEK`t|Bg3LKl`P>y|`;n3u@IH^b7eqDu~J6Z$R z^)vU>Q8LSK<^(py=TK=+GG$3AqLr+`rB->A=JKH%EsO+ig%dbF$!#K2Lm{OYro!DY z>7XMKqDNW`as^il%C2;X>kXx*4!u&hsEp>(pc#cI^5&eRYE`tw3dt9&5Q@jbh|lH_ zFJ>~ch$07=hw`QUyA+ri0L0Zkp;j&i{5eDG?os16ut5yqdx-nqdwF=2wv7O~d)K!n zH?b0o6@pAG$ZW^7mY8ZO;zl~D%56qm%WzqKK3Qw4Xy~NAae2QCbm&+wV zXddhwK7OUHD^>z!(Ov*}0sk6P{D9-6ohB?&o6}B{mdPB6fR_MX8kS%1frqe7&lfSX zR9Gk$WjSt!4D`nzY{UW5(HP0dE-op_*vwm!n3R)Rkds)MS_C1PoP%5)LtGU?9G!ez6+mK> z^|;YQ@&Zj~WiVxC RW02KVSDZY3zy2bH001Z~Dt!O| diff --git a/migrations/README b/migrations/README new file mode 100644 index 0000000..0e04844 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Single-database configuration for Flask. diff --git a/migrations/__pycache__/env.cpython-311.pyc b/migrations/__pycache__/env.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0205ce49b23ce3627cac4fcd88f7dd4fa5396bea GIT binary patch literal 5101 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 + return current_app.extensions['migrate'].db.engine + + +def get_engine_url(): + try: + return get_engine().url.render_as_string(hide_password=False).replace( + '%', '%%') + except AttributeError: + return str(get_engine().url).replace('%', '%%') + + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option('sqlalchemy.url', get_engine_url()) +target_db = current_app.extensions['migrate'].db + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_metadata(): + if hasattr(target_db, 'metadatas'): + return target_db.metadatas[None] + return target_db.metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=get_metadata(), literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + conf_args = current_app.extensions['migrate'].configure_args + if conf_args.get("process_revision_directives") is None: + conf_args["process_revision_directives"] = process_revision_directives + + connectable = get_engine() + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=get_metadata(), + **conf_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 0000000..2c01563 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/0de18b4ddaa3_initial_migration.py b/migrations/versions/0de18b4ddaa3_initial_migration.py new file mode 100644 index 0000000..0f61892 --- /dev/null +++ b/migrations/versions/0de18b4ddaa3_initial_migration.py @@ -0,0 +1,34 @@ +"""Initial migration + +Revision ID: 0de18b4ddaa3 +Revises: +Create Date: 2025-01-20 14:50:44.116314 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '0de18b4ddaa3' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('player', schema=None) as batch_op: + batch_op.add_column(sa.Column('user_id', sa.Integer(), nullable=True)) + batch_op.create_foreign_key('fk_user_id', 'user', ['user_id'], ['id']) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('player', schema=None) as batch_op: + batch_op.drop_constraint('fk_user_id', type_='foreignkey') + batch_op.drop_column('user_id') + + # ### end Alembic commands ### diff --git a/migrations/versions/__pycache__/0de18b4ddaa3_initial_migration.cpython-311.pyc b/migrations/versions/__pycache__/0de18b4ddaa3_initial_migration.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..149862fb7e884217012d90e4325d15f36b086d0e GIT binary patch literal 1812 zcmb_cO>7%Q6rR~#d&iqP#G$09b)}UeHL*}?J9Q9Ps8Z4hDH1h;a*2hstUXg_oAvH8 zyGbwtDF@D+kkSK4IiNu09*R_?95@h8JvxyQ(OwY;Zbsn{^~9T9+i{B817ddf+c)pM z`FU@?H{Yex8iMxi+g~=mk`ek{bOuf83nyg|b`V8XL@||Y+?1*k7P4&1RfQ_m1eL2v z_*D1^O?-i>DVhY-sC)-+~z6?;-=L_=-bA{sEd_gak$_s^ZsdT+qeEmkTq-lSOt>$njxn3o_ zFgjbkA4K#cfDk1*8OI``0C3Rm4P}T6J(Tt!`t~3LXaRkWK0~|1{$P~&2kKexo-&ZB zd`_Pj==VTq4?2h{4-?crXqST#AYjs9tpLbe;2k_O&`Rolar;bfxc zd;~&!2Th|Q@|7*=6L}SFVT@K0hDTU(sY;miY_rAq0~{v(`WkDRp}OHSZdmjK$SrX; zY}>5aEY$9<8+}8l2qBa#np3$5BFxmxpuT39b^s%T*x#hosJr$?(+MTt43oEFSzU1g z)_~!Y_2{6DyDn!|!!g!b3pPRGnOx5o`Ou%fmtS+6Ebm$QW#+F3u9v4)!-C~E8P5-y z<>#R*h~?);d0<#E1&yKOb*}~Sc|b_Y_$653W%&GG0k+W}sj+VA>ixCvgHEb+m@0Ks zr5=(J@8W%OOeTIKSB}V)Z8{=Qm5`RZHs(-P3dV2a|huG>*LS#FOQ;TK!psXz3 zRDZ!3unyW`6HF6_c=8j#|5YDUj#z%Ko+4fkIX_$<1e$!d0{#=5)e0BEa8a9l3;;+Q zlMA>7WKIMvj~P4#mYfn1myZjtmne5V;Hu*X+_an^9P29tdHF?fiOI;Pgm;R%TTV=% ztI*F1vncTA5@@#1AN`2vyV*w{c8GpRmbzrALzW`8WRFPp{^}u_>5`canGqbB73UX& zAjquX$B6FC`i#`)#q(YL3~Y60ZZRVtWR%5M)Pq+sU~Vi_4THLM!w6N=X3d&ak1JdA zKekONv!mcfN_K`b + + + Admin Panel + + + +
+

Admin Panel

+

Manage Users

+ + + + + + + + + + {% for user in users %} + + + + + + {% endfor %} + +
UsernameRoleActions
{{ user.username }}{{ user.role }} +
+ + +
+
+ +
+
+ +

Create New User

+
+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+ + + + \ No newline at end of file diff --git a/templates/dashboard.html b/templates/dashboard.html index debd26c..50412e3 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -10,6 +10,23 @@

Dashboard

+ +
+ Sign Out +
+ + + {% if current_user.role == 'admin' %} +
+
+

Users Management

+
+ +
+ {% endif %} +
@@ -18,8 +35,6 @@
    {% for player in players %} - -
  • {{ player.username }} ({{ player.ip }}) @@ -27,18 +42,20 @@
    View Schedule Full Screen + {% if current_user.role == 'admin' %}
    + {% endif %}
  • - - {% endfor %}
+ {% if current_user.role == 'admin' %} + {% endif %}
@@ -55,9 +72,11 @@ {% endfor %} + {% if current_user.role == 'admin' %} + {% endif %}
diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..c11439b --- /dev/null +++ b/templates/login.html @@ -0,0 +1,24 @@ + + + + Login + + + +
+

Login

+
+
+ + +
+
+ + +
+ +
+
+ + + \ No newline at end of file diff --git a/templates/register.html b/templates/register.html new file mode 100644 index 0000000..982a6f0 --- /dev/null +++ b/templates/register.html @@ -0,0 +1,24 @@ + + + + Register + + + +
+

Register

+
+
+ + +
+
+ + +
+ +
+
+ + + \ No newline at end of file