diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..0a9e463 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,23 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:$PROJECT_DIR$/instance/files.sqlite + + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:$PROJECT_DIR$/instance/files-test.sqlite + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..15a15b2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/files.danielcortes.xyz.iml b/.idea/files.danielcortes.xyz.iml new file mode 100644 index 0000000..28d3786 --- /dev/null +++ b/.idea/files.danielcortes.xyz.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..7c8f72d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..90c7974 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/files/__init__.py b/files/__init__.py index f2a9396..66bff24 100644 --- a/files/__init__.py +++ b/files/__init__.py @@ -3,42 +3,45 @@ import os from flask import Flask, render_template from werkzeug import SharedDataMiddleware + def create_app(): app = Flask(__name__, instance_relative_config=True) app.config.from_mapping( - DATABASE = os.path.join(app.instance_path, 'files.sqlite'), - USERNAME = 'dev', - PASSWORD = 'secret', - SECRET_KEY = '1337', - UPLOAD_FOLDER = 'uploads', - ALLOWED_EXTENSIONS = set(['png', 'jpg']) + SQLALCHEMY_DATABASE_URI="sqlite:///{}".format(os.path.join(app.instance_path, 'files.sqlite')), + SQLALCHEMY_TRACK_MODIFICATIONS=False, + USERNAME='dev', + PASSWORD='secret', + SECRET_KEY='1337', + UPLOAD_FOLDER='uploads' ) app.config.from_pyfile('config.py') - app.wsgi_app = SharedDataMiddleware(app.wsgi_app, { '/uploads': app.config['UPLOAD_FOLDER'] }) + app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {'/uploads': app.config['UPLOAD_FOLDER']}) try: os.makedirs(app.instance_path) except OSError: pass - from . import db + from files.models import db db.init_app(app) - from . import auth + from files import commands + commands.init_app(app) + + from files import auth app.register_blueprint(auth.bp) - from . import categories + from files import categories app.register_blueprint(categories.bp) - from . import about + from files import about app.register_blueprint(about.bp) app.add_url_rule('/about', endpoint='about') - from . import files + from files import files app.register_blueprint(files.bp) app.add_url_rule('/', endpoint='index') - return app diff --git a/files/about.py b/files/about.py index 3e240bb..da40fe7 100644 --- a/files/about.py +++ b/files/about.py @@ -1,9 +1,9 @@ -from files.db import get_db - from flask import Blueprint, request, render_template, redirect, url_for, flash +from files.models import db, Message bp = Blueprint('about', __name__, url_prefix='/about') + @bp.route('/', methods=('GET', 'POST')) def about(): if request.method == 'POST': @@ -11,7 +11,6 @@ def about(): email = request.form['email'] message = request.form['message'] - db = get_db() error = None if not name: @@ -20,21 +19,18 @@ def about(): error = 'Missing email' elif not message: error = 'Empty message' - + if error is not None: flash(error) else: - db.execute( - 'INSERT INTO messages' - ' (name, email, message)' - ' VALUES (?, ?, ?)', - (name, email, message) - ) - db.commit() - return redirect(url_for('about.thanks')) + m = Message(name, email, message) + db.session.add(m) + db.session.commit() + return redirect(url_for('about.thanks')) return render_template('about/about.html') + @bp.route('/thanks') def thanks(): return render_template('about/thanks.html') diff --git a/files/auth.py b/files/auth.py index 0735d7b..d028986 100644 --- a/files/auth.py +++ b/files/auth.py @@ -1,47 +1,46 @@ import functools -from files.db import get_db +from files.models import db, User from werkzeug.exceptions import abort from werkzeug.security import check_password_hash from flask import Blueprint, request, flash, render_template, session, g, redirect, url_for - bp = Blueprint('auth', __name__, url_prefix='/auth') + @bp.route('/login', methods=('GET', 'POST')) def login(): if request.method == 'POST': username = request.form['username'] password = request.form['password'] - db = get_db() error = None - user = db.execute( - 'SELECT * FROM users WHERE username = ?', (username,) - ).fetchone() + user = User.query.filter_by(username=username).first() if user is None: error = 'Incorrect username.' - elif not check_password_hash(user['password'], password): + elif not check_password_hash(user.password, password): error = 'Incorrect password.' if error is None: session.clear() - session['user_id'] = user['id'] + session['user_id'] = user.id return redirect(url_for('index')) flash(error) return render_template('auth/login.html') + @bp.route('/logout') def logout(): session.clear() return redirect(url_for('index')) + @bp.before_app_request def load_logged_in_user(): user_id = session.get('user_id') @@ -49,9 +48,7 @@ def load_logged_in_user(): if user_id is None: g.user = None else: - g.user = get_db().execute( - 'SELECT * FROM users WHERE id = ?', (user_id,) - ).fetchone() + g.user = User.query.get(user_id) def admin_required(view): @@ -63,4 +60,3 @@ def admin_required(view): return view(**kwargs) return wrapped_view - diff --git a/files/categories.py b/files/categories.py index b7991d8..f6c2c60 100644 --- a/files/categories.py +++ b/files/categories.py @@ -1,48 +1,15 @@ -from flask import Flask, Blueprint, flash, request, redirect, url_for, current_app, render_template, jsonify - -from werkzeug.exceptions import abort - -from files.db import get_db +from flask import Flask, Blueprint, flash, request, redirect, url_for, current_app, render_template, jsonify from files.auth import admin_required -from files.files import get_files_by_category +from files.models import Category, db bp = Blueprint('categories', __name__, url_prefix='/categories') -def get_categories(): - db = get_db() - categories = db.execute( - 'SELECT id, name' - ' FROM categories' - ' ORDER BY name DESC' - ).fetchall() - return categories - -def get_category(id): - db = get_db() - category = db.execute( - 'SELECT id, name' - ' FROM categories' - ' WHERE id = ?' - ' LIMIT 1', - (id,) - ).fetchone() - return category - -def get_files_by_category(id): - db = get_db() - files = db.execute( - 'SELECT id, filename, private' - ' FROM files' - ' WHERE category = ?' - ' ORDER BY filename DESC', - (id,) - ).fetchall() - return files @bp.route('/') def index(): - categories = get_categories() - return render_template('categories/index.html', categories=categories) + categories = Category.query.all() + return render_template('categories/index.html', categories=categories) + @bp.route('/create', methods=['GET', 'POST']) @admin_required @@ -57,21 +24,18 @@ def create(): if error is not None: flash(error) else: - db = get_db() - db.execute( - 'INSERT INTO categories (name)' - ' VALUES (?)', - (name,) - ) - db.commit() + c = Category(name) + db.session.add(c) + db.session.commit() return redirect(url_for('categories.index')) return render_template('categories/create.html') + @bp.route('') def view(id): - category = get_category(id) - files = get_files_by_category(id) + category = Category.query.get(id) + files = category.files return render_template('categories/view.html', category=category, files=files) @@ -80,9 +44,8 @@ def view(id): def update(): pass + @bp.route('//delete') @admin_required def delete(): pass - - diff --git a/files/commands.py b/files/commands.py new file mode 100644 index 0000000..dbd3f5a --- /dev/null +++ b/files/commands.py @@ -0,0 +1,90 @@ +import click +import os + +from flask import current_app +from flask.cli import with_appcontext + +from werkzeug.security import generate_password_hash + +from files.models import db, User, Category, File + + +def init_db(): + db.drop_all() + db.create_all() + + +def generate_admin(): + username = current_app.config['USERNAME'] + password = current_app.config['PASSWORD'] + + u = User(username, generate_password_hash(password)) + db.session.add(u) + db.session.commit() + + +def add_defaults(): + default_category = Category('Default') + db.session.add(default_category) + db.session.commit() + + +def add_files(): + existing_files = os.listdir(current_app.config['UPLOAD_FOLDER']) + default_category = Category.query.filter_by(name='Default').first() + added = 0 + + for f in existing_files: + search = File.query.filter_by(filename=f).first() + if search is None: + file = File(f, 0, default_category.id) + db.session.add(file) + added += 1 + db.session.commit() + + return added + + +@click.command('init-db') +@with_appcontext +def init_db_command(): + """ + Creates the db file + """ + init_db() + + +@click.command("add-defaults") +@with_appcontext +def add_defaults_command(): + """ + Initializes the database with default data + """ + add_defaults() + click.echo("Defaults added") + + +@click.command('generate-admin') +@with_appcontext +def generate_admin_command(): + """Creates the admin of the system""" + generate_admin() + click.echo('The admin was created') + + +@click.command('add-files') +@with_appcontext +def add_files_command(): + """ + Generates the rows in the database for the files currently uploaded + They all will have default privacy, so, public + """ + added_size= add_files() + click.echo(f'added {added_size} files') + + +def init_app(app): + app.cli.add_command(init_db_command) + app.cli.add_command(add_defaults_command) + app.cli.add_command(generate_admin_command) + app.cli.add_command(add_files_command) diff --git a/files/db.py b/files/db.py deleted file mode 100644 index 66ccf47..0000000 --- a/files/db.py +++ /dev/null @@ -1,77 +0,0 @@ -import sqlite3 -import click - -from flask import current_app, g -from flask.cli import with_appcontext -from werkzeug.security import generate_password_hash - -def get_db(): - if 'db' not in g: - g.db = sqlite3.connect( - current_app.config['DATABASE'], - detect_types = sqlite3.PARSE_DECLTYPES - ) - g.db.row_factory = sqlite3.Row - return g.db - -def close_db(e=None): - db = g.pop('db', None) - - if db is not None: - db.close() - -def init_db(): - db = get_db() - - with current_app.open_resource('schema.sql') as f: - db.executescript(f.read().decode('utf8')) - -def generate_admin(): - db = get_db() - username = current_app.config['USERNAME'] - password = current_app.config['PASSWORD'] - - db.execute( - 'INSERT INTO users (username, password) VALUES (?, ?)', - (username, generate_password_hash(password),) - ) - db.commit() - -def generate_files(): - pass - - -@click.command('init-db') -@with_appcontext -def init_db_command(): - """Inits the database with it's schema""" - init_db() - click.echo('The database was initialized') - -@click.command('generate-admin') -@with_appcontext -def generate_admin_command(): - """Creates the admin of the system""" - generate_admin() - click.echo('The admin was created') - - -@click.command('generate-files') -@with_appcontext -def generate_files_command(): - """ - Generates the rows in the database for the files currently uploaded - They all will have default privacy, so, public - """ - generate_files() - click.echo('files generated') - - -def init_app(app): - app.teardown_appcontext(close_db) - app.cli.add_command(init_db_command) - app.cli.add_command(generate_admin_command) - - - - diff --git a/files/files.py b/files/files.py index 8a4e068..b7f248d 100644 --- a/files/files.py +++ b/files/files.py @@ -1,155 +1,74 @@ import os -import random -from flask import Flask, Blueprint, flash, request, redirect, url_for, current_app, render_template, send_from_directory, g +from flask import Flask, Blueprint, flash, request, redirect, url_for, current_app, render_template, \ + send_from_directory, g from werkzeug.utils import secure_filename from werkzeug.exceptions import abort +from files.models import db, File, Category, User from files.auth import admin_required -from files.db import get_db - bp = Blueprint('files', __name__) bp.add_url_rule('/uploads/', 'uploaded_file', build_only=True) -def get_extension(filename): - return filename.rsplit('.', 1)[1].lower() -def get_path_in_upload(filename): +def _get_extension(filename): + return filename.rsplit('.', 1)[1].lower() + + +def _get_path_in_upload(filename): return os.path.join(current_app.config['UPLOAD_FOLDER'], filename) -def save_file(file, private, category): + +def _save_file(file, private, category): if private is None: is_private = 0 else: is_private = 1 - db = get_db() - filename = secure_filename(file.filename) - db.execute( - 'INSERT INTO files (filename, private, category)' - ' VALUES (?,?,?)', - (filename, is_private, category['id'],) - ) - file.save(get_path_in_upload(filename)) + file.save(_get_path_in_upload(filename)) + f = File(filename, is_private, category.id) - db.commit() + db.session.add(f) + db.session.commit() - return filename def _rename_file(file, new_name): - db = get_db() - new_name = secure_filename(new_name) - db.execute( - 'UPDATE files' - ' SET filename = ?' - ' WHERE id = ?', - (new_name, file['id'],) - ) - os.rename(get_path_in_upload(file['filename']), get_path_in_upload(new_name)) + os.rename(_get_path_in_upload(file.filename), _get_path_in_upload(new_name)) + file.filename = new_name - db.commit() - - return new_name + db.session.commit() def _delete_file(file): - db = get_db() - db.execute( - 'DELETE from files' - ' WHERE id = ?', - (file['id'],) - ) - - os.remove(get_path_in_upload(file['filename'])) - db.commit() + os.remove(_get_path_in_upload(file.filename)) + db.session.delete(file) + db.session.commit() -def get_files(): - db = get_db() - files = db.execute( - 'SELECT id, filename, private, category' - ' FROM files' - ' ORDER BY filename' - ).fetchall() - return files - -def get_public_files(): - db = get_db() - files = db.execute( - 'SELECT id, filename, private, category' - ' FROM files' - ' WHERE private = false' - ' ORDER BY filename' - ).fetchall() - - return files - -def get_files_by_category(id): - db = get_db() - files = db.execute( - 'SELECT id, filename, private' - ' FROM files' - ' WHERE category = ?' - ' ORDER BY filename', - (id,) - ).fetchall() - -def get_file(id): - db = get_db() - file = db.execute( - 'SELECT id, filename, private, category' - ' FROM files' - ' WHERE id = ?' - ' LIMIT 1', - (id,) - ).fetchone() - return file - -def get_categories(): - db = get_db() - categories = db.execute( - 'SELECT id, name' - ' FROM categories' - ' ORDER BY name' - ).fetchall() - return categories - -def get_category(id): - db = get_db() - category = db.execute( - 'SELECT id, name' - ' FROM categories' - ' WHERE id = ?' - ' LIMIT 1', - (id,) - ).fetchone() - return category - -@bp.route('/', methods=['GET', 'POST']) +@bp.route('/', methods=['GET', 'POST']) def index(): if g.user is None: - files = get_public_files() - else: - files = get_files() + files = File.query.filter_by(private=0).all() + else: + files = File.query.all() return render_template('files/index.html', files=files) + @bp.route('/upload', methods=['GET', 'POST']) @admin_required def upload_file(): - db = get_db() - if request.method == 'POST': if 'file' not in request.files: flash('No file part') return redirect(request.url) - else: + else: file = request.files['file'] if 'private' not in request.form: @@ -160,23 +79,27 @@ def upload_file(): if 'category' not in request.form: flash('No category selected') return redirect(request.url) - else: - category = get_category(request.form['category']) + + category = Category.query.get(request.form['category']) + if category is None: + flash('The category selected won\'t exists') + return redirect(request.url) if file.filename == '': flash('No seleted file') return redirect(request.url) if file: - filename = save_file(file, private, category) + _save_file(file, private, category) return redirect(url_for('files.index')) - - return render_template('files/upload.html', categories=get_categories()) + + return render_template('files/upload.html', categories=Category.query.all()) + @bp.route('/preview/') def preview_file(id): - file = get_file(id) - if (file['private'] == 1 and g.user is not None) or (file['private'] == 0): + file = File.query.get(id) + if (file.private == 1 and g.user is not None) or (file.private == 0): return render_template('files/preview.html', file=file) else: return abort(404) @@ -186,28 +109,27 @@ def preview_file(id): @admin_required def rename_file(id): if request.method == 'POST': - file = get_file(id) + file = File.query.get(id) new_name = request.form['new_name'].lower() - extension = file['filename'].rsplit('.', 1)[1].lower() + extension = file.filename.rsplit('.', 1)[1].lower() - if "." in new_name and get_extension(new_name): - new_name = new_name.rsplit('.',1)[0] + '.' + extension + if "." in new_name and _get_extension(new_name): + new_name = new_name.rsplit('.', 1)[0] + '.' + extension else: new_name = new_name + '.' + extension - + _rename_file(file, new_name) - - return redirect(url_for('files.preview_file', id=file['id'])) + + return redirect(url_for('files.preview_file', id=file.id)) + @bp.route('/delete/', methods=['POST']) @admin_required def delete_file(id): if request.method == 'POST': - file = get_file(id) - print(file) + file = File.query.get(id) _delete_file(file) return redirect(url_for('index')) else: abort(404) - diff --git a/files/models.py b/files/models.py new file mode 100644 index 0000000..a17f55d --- /dev/null +++ b/files/models.py @@ -0,0 +1,64 @@ +from flask_sqlalchemy import SQLAlchemy +from datetime import datetime + +db = SQLAlchemy() + + +class User(db.Model): + __tablename__ = 'users' + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String, unique=True, nullable=False) + password = db.Column(db.String, nullable=False) + + def __init__(self, username=None, password=None): + self.username = username + self.password = password + + def __repr__(self): + return f'' + + +class Message(db.Model): + __tablename__ = 'messages' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String, nullable=False) + email = db.Column(db.String, nullable=False) + message = db.Column(db.Text, nullable=False) + sended = db.Column(db.DateTime, default=datetime.utcnow()) + + def __init__(self, name=None, email=None, message=None): + self.name = name + self.email = email + self.message = message + + def __repr__(self): + return f'' + + +class File(db.Model): + __tablename__ = 'files' + id = db.Column(db.Integer, primary_key=True) + filename = db.Column(db.String, nullable=False) + private = db.Column(db.Integer, nullable=False) + category_id = db.Column(db.Integer, db.ForeignKey('categories.id'), nullable=False) + + def __init__(self, filename=None, private=None, category_id=None): + self.filename = filename + self.private = private + self.category_id = category_id + + def __repr__(self): + return f'' + + +class Category(db.Model): + __tablename__ = 'categories' + id = db.Column(db.Integer, primary_key=True) + name = db.Column(db.String, nullable=False) + files = db.relationship('File', backref='category', lazy=True) + + def __init__(self, name=None): + self.name = name + + def __repr__(self): + return f'' diff --git a/files/schema.sql b/files/schema.sql deleted file mode 100644 index fad2c20..0000000 --- a/files/schema.sql +++ /dev/null @@ -1,33 +0,0 @@ -DROP TABLE IF EXISTS users; -DROP TABLE IF EXISTS messages; -DROP TABLE IF EXISTS files; -DROP TABLE IF EXISTS categories; - -CREATE TABLE users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - username TEXT NOT NULL, - password TEXT NOT NULL -); - -CREATE TABLE messages ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - email TEXT NOT NULL, - message TEXT NOT NULL, - sended TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - -CREATE TABLE files ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - filename TEXT NOT NULL, - private INTEGER NOT NULL DEFAULT 0, - category INTEGER NOT NULL, - FOREIGN KEY(category) REFERENCES categories(id) -); - -CREATE TABLE categories ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL -); - -