diff options
author | rubenwardy <rw@rubenwardy.com> | 2018-03-18 17:43:30 +0000 |
---|---|---|
committer | rubenwardy <rw@rubenwardy.com> | 2018-03-18 17:43:30 +0000 |
commit | 366a2302d092c12f388f0eb8efb4faaa3acd3303 (patch) | |
tree | 070f3ef21b04bd716b9136a827d9fe7b90c32e2c | |
download | cheatdb-366a2302d092c12f388f0eb8efb4faaa3acd3303.tar.xz |
Initial commit
-rw-r--r-- | .gitignore | 172 | ||||
-rw-r--r-- | app/__init__.py | 7 | ||||
-rw-r--r-- | app/models.py | 72 | ||||
-rw-r--r-- | app/static/screenshot.png | bin | 0 -> 228922 bytes | |||
-rw-r--r-- | app/static/style.css | 245 | ||||
-rw-r--r-- | app/templates/base.html | 74 | ||||
-rw-r--r-- | app/templates/flask_user/login.html | 76 | ||||
-rw-r--r-- | app/templates/flask_user/public_base.html | 10 | ||||
-rw-r--r-- | app/templates/index.html | 35 | ||||
-rw-r--r-- | app/views/__init__.py | 74 | ||||
-rw-r--r-- | config.example.cfg | 6 | ||||
-rw-r--r-- | requirements.txt | 5 | ||||
-rw-r--r-- | rundebug.py | 3 | ||||
-rw-r--r-- | setup.py | 17 |
14 files changed, 796 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..db3f938 --- /dev/null +++ b/.gitignore @@ -0,0 +1,172 @@ +config.cfg +*.sqlite +main.css + + +# Created by https://www.gitignore.io/api/linux,macos,python,windows + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +.pytest_cache/ +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule.* + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +# End of https://www.gitignore.io/api/linux,macos,python,windows diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..fb0ef3a --- /dev/null +++ b/app/__init__.py @@ -0,0 +1,7 @@ +from flask import * +from flask_user import * + +app = Flask(__name__) +app.config.from_pyfile('../config.cfg') + +import models, views diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000..6129330 --- /dev/null +++ b/app/models.py @@ -0,0 +1,72 @@ +from flask import Flask, url_for +from flask.ext.sqlalchemy import SQLAlchemy +from app import app +from datetime import datetime +from sqlalchemy.orm import validates +from flask_user import login_required, UserManager, UserMixin, SQLAlchemyAdapter + +# Initialise database +db = SQLAlchemy(app) + +def title_to_url(title): + return title.lower().replace(" ", "_") + +def url_to_title(url): + return url.replace("_", " ") + +class User(db.Model, UserMixin): + id = db.Column(db.Integer, primary_key=True) + + # User authentication information + username = db.Column(db.String(50), nullable=False, unique=True) + password = db.Column(db.String(255), nullable=False, server_default='') + reset_password_token = db.Column(db.String(100), nullable=False, server_default='') + + # User email information + email = db.Column(db.String(255), nullable=True, unique=True) + confirmed_at = db.Column(db.DateTime()) + + # User information + active = db.Column('is_active', db.Boolean, nullable=False, server_default='0') + display_name = db.Column(db.String(100), nullable=False, server_default='') + + # Content + mods = db.relationship('Mod', backref='author', lazy='dynamic') + + def __init__(self, username): + import datetime + + self.username = username + self.confirmed_at = datetime.datetime.now() - datetime.timedelta(days=6000) + + def isClaimed(self): + return self.password is not None and self.password != "" + +class Role(db.Model): + id = db.Column(db.Integer(), primary_key=True) + name = db.Column(db.String(50), unique=True) + description = db.Column(db.String(255)) + +class UserRoles(db.Model): + id = db.Column(db.Integer(), primary_key=True) + user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE')) + role_id = db.Column(db.Integer(), db.ForeignKey('role.id', ondelete='CASCADE')) + +class Mod(db.Model): + id = db.Column(db.Integer, primary_key=True) + + # Basic details + author_id = db.Column(db.Integer, db.ForeignKey('user.id')) + name = db.Column(db.String(100), nullable=False) + title = db.Column(db.String(100), nullable=False) + desc = db.Column(db.Text, nullable=True) + + # Downloads + repo = db.Column(db.String(200), nullable=True) + website = db.Column(db.String(200), nullable=True) + issueTracker = db.Column(db.String(200), nullable=True) + forums = db.Column(db.String(200), nullable=False) + +# Setup Flask-User +db_adapter = SQLAlchemyAdapter(db, User) # Register the User model +user_manager = UserManager(db_adapter, app) # Initialize Flask-User diff --git a/app/static/screenshot.png b/app/static/screenshot.png Binary files differnew file mode 100644 index 0000000..3cbf879 --- /dev/null +++ b/app/static/screenshot.png diff --git a/app/static/style.css b/app/static/style.css new file mode 100644 index 0000000..c9c28d6 --- /dev/null +++ b/app/static/style.css @@ -0,0 +1,245 @@ +html, body { + font-family: "Arial", sans-serif; + background: #222; + color: #ddd; + padding: 0; + margin: 0; +} + +h1 { + text-align: center; +} + + +h2, h3 { + margin: 5px 0; +} + +a { + color: #0be; + font-weight: bold; + text-decoration: none; +} + +a:hover { + color: #0df; + text-decoration: underline; +} + +/* Containers */ + +.box { + border-radius: 5px; + margin: 15px 0; +} + +.box_grey { + padding: 10px; + background: #333; + border: 1px solid #444; +} + +.ul_boxes { + display: block; + margin: 0; + padding: 0; + list-style: none; +} + +.ul_boxes > li { + padding: 0; + list-style: none; +} + +.box_link { + display: block; + color: #ddd; + text-decoration: none; +} + +.box_link:hover{ + background: #3a3a3a; +} + +/* + buttonset +*/ + +.buttonset, .buttonset li { + display: block; + margin: 0; + padding: 0; + list-style: none; +} + +.buttonset { + margin: 15px 0; +} + +.buttonset li a { + text-align: center; + color: #ddd; + text-decoration: none; + margin: 5px 0 !important; +} + +.buttonset li a:hover { + background: #444; +} + +.btn_green { + background: #363 !important; + border: 1px solid #473; +} + +.btn_green:hover { + background: #474 !important; +} + +/* Alerts */ + +#alerts { + list-style: none; + position: fixed; + bottom: 15px; + left: 0; + right: 0; +} + +#alerts .alert { + margin: 5px 0; + vertical-align: middle; +} + +#alerts .close { + float: right; + color: white; +} + +#alerts .close:hover { + color: #fff; +} + +.alert-error { + background: #933; + border: 1px solid #c44; +} + +.alert-warning { + background: #963; + border: 1px solid #c96; +} + +/* Nav */ + +nav, main, #alerts { + width: 90%; + max-width: 960px; + margin: auto; + padding: 0; +} + +nav { + margin: 15px auto 5px auto; + list-style: none; + background: #333; + border-radius: 5px; + border: 1px solid #444; +} + +nav .navbar-nav { + float: left; +} + +nav .navbar-right { + float: right; +} + +nav ul { + margin: 0 auto 0 auto; + padding: 0; + list-style: none; +} + +nav li { + margin: 0; + padding: 0; + list-style: none; + display: inline-block; +} + +nav li a { + color: #ddd; + margin: 0; + padding: 10px 20px; + display: block; + border-left: 1px solid #444; +} + +nav a:hover { + color: #eee; + background: #444; + text-decoration: none; +} + + +/* Footer */ + +footer { + width: 80%; + max-width: 860px; + margin: auto; + padding: 50px 0 20px 0; +} + +footer a { + color: #666; +} + + +/* Mod */ + +.box_img { + position: relative; + background-position: center; + background-size: cover; + background-image: url("screenshot.png"); + min-height: 220px; + border-radius: 5px; + padding: 0; +} + +.box_img > h2 { + display: inline-block; + position: absolute; + bottom: 15px; + left: 15px; +} + +.sidebar_container { + display: block; + position: relative; + padding: 0; + margin: 0; +} + +.sidebar_container .right, .sidebar_container .left{ + position: absolute; + display: block; + top: 10px; + margin-top: 0; +} + +.sidebar_container .right { + right: 0; + width: 280px; +} + +.sidebar_container .left { + right: 295px; + left: 0; +} + +.sidebar_container .right > *:first-child, .sidebar_container .left > *:first-child { + margin-top: 0; +} diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 0000000..6e8c372 --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,74 @@ +<!DOCTYPE html> +<html> + +<head> + <meta charset="utf-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>{% block title %}title{% endblock %} - {{ config.USER_APP_NAME }}</title> + <link rel="stylesheet" type="text/css" href="/static/style.css"> +</head> + +<body> + <nav> + <ul class="nav navbar-nav"> + <li><a href="/">{{ config.USER_APP_NAME }}</a></li> + {% for item in current_menu.children recursive %} + <li{% if item.children %} class="dropdown"{% endif %}> + <a href="{{ item.url }}" + {% if item.children %} + class="dropdown-toggle" + data-toggle="dropdown" + role="button" + aria-expanded="false" + {% endif %}> + {{ item.text }} + {% if item.children %} + <span class="caret"></span> + {% endif %} + </a> + {% if item.children %} + <ul class="dropdown-menu" role="menu"> + {{ loop(item.children) }} + </ul> + {% endif %} + </li> + {% endfor %} + </ul> + <ul class="nav navbar-nav navbar-right"> + {% if current_user.is_authenticated %} + <li><a href="{{ url_for('user_profile_page') }}">{{ current_user.first_name or current_user.username }}</a></li> + <li><a href="{{ url_for('user.logout') }}">Sign out</a></li> + {% else %} + <li><a href="{{ url_for('user.login') }}">Sign in</a></li> + {% endif %} + </ul> + <div style="clear:both;"></div> + </nav> + + + {% block flash_messages %} + {%- with messages = get_flashed_messages(with_categories=true) -%} + {% if messages %} + <ul id="alerts"> + {% for category, message in messages %} + <li class="box box_grey alert alert-{{category}}"> + <span class="icon_message"></span> + + {{ message|safe }} + + <div style="clear: both;"></div> + </li> + {% endfor %} + </ul> + {% endif %} + {%- endwith %} + {% endblock %} + + {% block container %} + <main> + {% block content %} + {% endblock %} + </main> + {% endblock %} +</html> diff --git a/app/templates/flask_user/login.html b/app/templates/flask_user/login.html new file mode 100644 index 0000000..44274bb --- /dev/null +++ b/app/templates/flask_user/login.html @@ -0,0 +1,76 @@ +{% extends "base.html" %} + +{% block title %} +Sign in +{% endblock %} + +{% block content %} +<div class="sidebar_container"> + <div class="left box box_grey"> + {% from "flask_user/_macros.html" import render_field, render_checkbox_field, render_submit_field %} + <h2>{%trans%}Sign in{%endtrans%}</h2> + + <form action="" method="POST" class="form" role="form"> + {{ form.hidden_tag() }} + + {# Username or Email field #} + {% set field = form.username if user_manager.enable_username else form.email %} + <div class="form-group {% if field.errors %}has-error{% endif %}"> + {# Label on left, "New here? Register." on right #} + <div class="row"> + <div class="col-xs-6"> + <label for="{{ field.id }}" class="control-label">{{ field.label.text }}</label> + </div> + </div> + {{ field(class_='form-control', tabindex=110) }} + {% if field.errors %} + {% for e in field.errors %} + <p class="help-block">{{ e }}</p> + {% endfor %} + {% endif %} + </div> + + {# Password field #} + {% set field = form.password %} + <div class="form-group {% if field.errors %}has-error{% endif %}"> + {# Label on left, "Forgot your Password?" on right #} + <div class="row"> + <div class="col-xs-6"> + <label for="{{ field.id }}" class="control-label">{{ field.label.text }}</label> + </div> + <div class="col-xs-6 text-right"> + {% if user_manager.enable_forgot_password %} + <a href="{{ url_for('user.forgot_password') }}" tabindex='195'> + {%trans%}Forgot your Password?{%endtrans%}</a> + {% endif %} + </div> + </div> + {{ field(class_='form-control', tabindex=120) }} + {% if field.errors %} + {% for e in field.errors %} + <p class="help-block">{{ e }}</p> + {% endfor %} + {% endif %} + </div> + + {# Remember me #} + {% if user_manager.enable_remember_me %} + {{ render_checkbox_field(login_form.remember_me, tabindex=130) }} + {% endif %} + + {# Submit button #} + {{ render_submit_field(form.submit, tabindex=180) }} + </form> + </div> + + <div class="right"> + <aside class="box box_grey"> + <h2>New here?</h2> + + {% if user_manager.enable_register and not user_manager.require_invitation %} + <a href="">{%trans%}Create an account{%endtrans%}</a> + {% endif %} + </aside> + </div> +</div> +{% endblock %} diff --git a/app/templates/flask_user/public_base.html b/app/templates/flask_user/public_base.html new file mode 100644 index 0000000..1dddd67 --- /dev/null +++ b/app/templates/flask_user/public_base.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block container %} + <main> + <div class="box box_grey"> + {% block content %} + {% endblock %} + </div> + </main> +{% endblock %} diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000..541b230 --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,35 @@ +{% extends "base.html" %} + +{% block title %} +Dashboard +{% endblock %} + +{% block content %} + <div class="box box_grey"> + <h2>{{ self.title() }}</h2> + + {% if current_user.is_authenticated %} + <p> + Hello user! + </p> + {% else %} + <p> + Please login! + </p> + {% endif %} + </div> + + <div class="2box"> + <div class="box box_grey"> + <h2>Top Mods</h2> + </div> + <div class="box box_grey"> + <h2>Statistics</h2> + <ul> + <li>Total mods: 543</li> + <li>Missing mods: 1020</li> + <li>Downloads/day: 200</li> + </ul> + </div> + </div> +{% endblock %} diff --git a/app/views/__init__.py b/app/views/__init__.py new file mode 100644 index 0000000..deb5ac5 --- /dev/null +++ b/app/views/__init__.py @@ -0,0 +1,74 @@ +from app import app +from flask import * +from flask_user import * +from flask_login import login_user, logout_user +from app.models import * +from flask.ext import menu, markdown +from sqlalchemy import func +from werkzeug.contrib.cache import SimpleCache +cache = SimpleCache() + +menu.Menu(app=app) +markdown.Markdown(app, extensions=['fenced_code']) + +# TODO: remove on production! +@app.route('/static/<path:path>') +def send_static(path): + return send_from_directory('static', path) + +@app.route('/') +@menu.register_menu(app, '.', 'Home') +def home_page(): + return render_template('index.html') + +# Define the User registration form +# It augments the Flask-User RegisterForm with additional fields +from flask_user.forms import RegisterForm +from flask_wtf import FlaskForm +from wtforms import StringField, SubmitField, validators +class MyRegisterForm(RegisterForm): + first_name = StringField('First name', validators=[ + validators.DataRequired('First name is required')]) + last_name = StringField('Last name', validators=[ + validators.DataRequired('Last name is required')]) + +# Define the User profile form +class UserProfileForm(FlaskForm): + first_name = StringField('First name', validators=[ + validators.DataRequired('First name is required')]) + last_name = StringField('Last name', validators=[ + validators.DataRequired('Last name is required')]) + submit = SubmitField('Save') + +@app.route('/user/', methods=['GET', 'POST']) +@app.route('/user/<username>/', methods=['GET']) +def user_profile_page(username=None): + user = None + form = None + if username is None: + if not current_user.is_authenticated: + return current_app.login_manager.unauthorized() + user = current_user + else: + user = User.query.filter_by(username=username).first() + if not user: + abort(404) + + if user == current_user: + # Initialize form + form = UserProfileForm(request.form, current_user) + + # Process valid POST + if request.method=='POST' and form.validate(): + # Copy form fields to user_profile fields + form.populate_obj(current_user) + + # Save user_profile + db.session.commit() + + # Redirect to home page + return redirect(url_for('home_page')) + + # Process GET or invalid POST + return render_template('users/user_profile_page.html', + user=user, form=form) diff --git a/config.example.cfg b/config.example.cfg new file mode 100644 index 0000000..7fffe2c --- /dev/null +++ b/config.example.cfg @@ -0,0 +1,6 @@ +USER_APP_NAME="Content DB" + +SECRET_KEY="" +WTF_CSRF_SECRET_KEY="" + +SQLALCHEMY_DATABASE_URI = "sqlite:///../db.sqlite" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4e52acf --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +Flask>=0.10.1 +Flask-SQLAlchemy>=2.1 +Flask-User>=0.6.9 +Flask-Menu>=0.5.0 +Flask-Markdown>=0.3 diff --git a/rundebug.py b/rundebug.py new file mode 100644 index 0000000..e4592d2 --- /dev/null +++ b/rundebug.py @@ -0,0 +1,3 @@ +from app import app + +app.run(host='0.0.0.0', port=5000, debug=True) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..0ba2b5a --- /dev/null +++ b/setup.py @@ -0,0 +1,17 @@ +import os, datetime + +delete_db = False + +if delete_db and os.path.isfile("app/data.sqlite"): + os.remove("app/data.sqlite") + +if not os.path.isfile("app/data.sqlite"): + from app import models + + print("Creating database tables...") + models.db.create_all() + + print("Filling database...") + models.db.session.commit() +else: + print("Database already exists") |