commit 877a246134265683bd5f83da738fde8742e948a8 Author: usernames122 Date: Sun Aug 10 23:42:02 2025 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cb0f8dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,203 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# 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/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock +#poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +#pdm.lock +#pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +#pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml diff --git a/app.py b/app.py new file mode 100644 index 0000000..bea796f --- /dev/null +++ b/app.py @@ -0,0 +1,44 @@ +import os +import importlib +from flask import Flask, Blueprint +from models import db +import secrets +app = Flask(__name__, static_folder='static', static_url_path='/static') + +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db' +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False +if os.path.isfile("secret.key"): + with open("secret.key", "r") as f: + app.config['SECRET_KEY'] = f.read().strip() +else: + app.config['SECRET_KEY'] = secrets.token_hex(16) + with open("secret.key", "w") as f: + f.write(app.config['SECRET_KEY']) + +def register_blueprints(app, package_name, package_path): + """ + Auto-register all blueprints found in the package folder. + Supports files like index.py as well. + Each module must expose a blueprint named `_bp`. + """ + for filename in os.listdir(package_path): + # Accept .py files including index.py, except __init__.py + if filename.endswith('.py') and filename != '__init__.py': + module_name = filename[:-3] # strip .py + module = importlib.import_module(f"{package_name}.{module_name}") + + # Register all blueprint instances in the module + for attr_name in dir(module): + attr = getattr(module, attr_name) + if isinstance(attr, Blueprint): + app.register_blueprint(attr) + +register_blueprints(app, 'pages', os.path.join(os.path.dirname(__file__), 'pages')) + +db.init_app(app) + +with app.app_context(): + db.create_all() # if you want to create tables + +if __name__ == '__main__': + app.run(debug=True) diff --git a/goodrun.sh b/goodrun.sh new file mode 100644 index 0000000..6c38b5c --- /dev/null +++ b/goodrun.sh @@ -0,0 +1,3 @@ +#!/bin/bash +source ./.venv/bin/activate +gunicorn -w 4 -b 0.0.0.0:8000 app:app \ No newline at end of file diff --git a/models.py b/models.py new file mode 100644 index 0000000..5fdb12e --- /dev/null +++ b/models.py @@ -0,0 +1,17 @@ +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() + +class User(db.Model): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(80), unique=True, nullable=False) + password = db.Column(db.String(128), nullable=False) # hashed password storage + register_time = db.Column(db.DateTime, nullable=False) + +class Finding(db.Model): + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(120), nullable=False) + path = db.Column(db.String(120), nullable=False) # Path on laminax.org + find_time = db.Column(db.DateTime, nullable=False) + found_by = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + content_preview = db.Column(db.Text, nullable=True) # Scraped preview of the finding \ No newline at end of file diff --git a/pages/findings.py b/pages/findings.py new file mode 100644 index 0000000..0749d33 --- /dev/null +++ b/pages/findings.py @@ -0,0 +1,122 @@ +from flask import Blueprint, render_template +from models import Finding, User +from sqlalchemy import desc + +findings_bp = Blueprint('findings', __name__, url_prefix='/findings') + +@findings_bp.route('/') +def latest_findings(): + latest = Finding.query.order_by(desc(Finding.find_time)).limit(20).all() + # Eager load user data if needed + user_map = {u.id: u for u in User.query.filter(User.id.in_([f.found_by for f in latest])).all()} + return render_template('latest_findings.html', findings=latest, user_map=user_map) + +@findings_bp.route('/') +def finding_detail(finding_id): + finding = Finding.query.get_or_404(finding_id) + user = User.query.get(finding.found_by) + return render_template('finding_detail.html', finding=finding, user=user) + + + +import requests +from flask import Blueprint, render_template, request, session, flash, redirect, url_for +from datetime import datetime +from bs4 import BeautifulSoup +from models import db, Finding + + +@findings_bp.route('/create', methods=['GET', 'POST']) +def create_finding(): + if not session.get('loggedin'): + flash("Please log in to create a finding.", "warning") + return redirect(url_for('login.login')) + + if request.method == 'POST': + path = request.form.get('path', '').strip() + lorekey = request.form.get('lorekey', '').strip() + + # Validate inputs + if not path and not lorekey: + flash("Title, Path, and Lorekey are required.", "danger") + return render_template('create_finding.html', path=path, lorekey=lorekey) + + # Validate path exists on laminax.org (non-404) + if path: + try: + path_res = requests.get(f'https://laminax.org/{path}') + if path_res.status_code == 404: + flash(f"The path '{path}' does not exist on laminax.org.", "danger") + return render_template('create_finding.html', path=path, lorekey=lorekey) + else: + soup = BeautifulSoup(path_res.text, 'html.parser') + for hr in soup.find_all('hr'): + hr.replace_with('----------') + content_text = soup.get_text(separator='\n') + content_text = soup.get_text(separator='\n') + # Get title element + title = (soup.title.string if soup.title else None) or "No title found" + # Save finding + new_finding = Finding( + title=f'https://laminax.org/{path}', + path=f'https://laminax.org/{path}', + find_time=datetime.utcnow(), + found_by=session.get('id'), + content_preview=content_text + ) + db.session.add(new_finding) + db.session.commit() + flash("Finding created successfully!", "success") + return redirect("/findings/"+str(new_finding.id)) # Resort to manually redirecting for now + except Exception as e: + flash(f"Error validating path: {e}", "danger") + return render_template('create_finding.html', path=path, lorekey=lorekey) + + # Check lorekey with external service + if lorekey: + try: + res = requests.post('https://worker.laminax.org/check-password', json={"password": lorekey}) + if res.ok: + data = res.json() + if data.get('redirect'): + redirect_url = data['redirect'] + + # Fetch redirect page content + page_res = requests.get(redirect_url) + title = None + if page_res.ok: + # Parse html and replace all
with 10 dashes using bs4 + soup = BeautifulSoup(page_res.text, 'html.parser') + for hr in soup.find_all('hr'): + hr.replace_with('----------') + content_text = soup.get_text(separator='\n') + # Get title element + title = (soup.title.string if soup.title else None) or "No title found" + else: + content_text = None + title = "Unable to fetch redirect page content." + + # Save finding + new_finding = Finding( + title=redirect_url, + path=redirect_url, + find_time=datetime.utcnow(), + found_by=session.get('id'), + content_preview=content_text + ) + db.session.add(new_finding) + db.session.commit() + flash("Finding created successfully!", "success") + return redirect(url_for('findings.finding_detail', finding_id=new_finding.id)) + else: + flash("Lorekey check failed or no redirect returned.", "danger") + elif res.status_code == 401: + flash("Invalid Lorekey provided.", "danger") + else: + flash("Lorekey service error, try again later.", "danger") + except Exception as e: + flash(f"An error occurred: {e}", "danger") + + # GET or fallback render + return render_template('create_finding.html') + diff --git a/pages/index.py b/pages/index.py new file mode 100644 index 0000000..10a209b --- /dev/null +++ b/pages/index.py @@ -0,0 +1,7 @@ +from flask import Blueprint, render_template +from models import db, User +index_bp = Blueprint('index', __name__) + +@index_bp.route('/') +def index(): + return render_template('home.html',users=User.query.limit(20).all()) # or your index page template diff --git a/pages/login.py b/pages/login.py new file mode 100644 index 0000000..6e7433d --- /dev/null +++ b/pages/login.py @@ -0,0 +1,56 @@ +from flask import Blueprint, render_template, request, redirect, url_for, session +from werkzeug.security import check_password_hash +from models import db, User + +login_bp = Blueprint('login', __name__, url_prefix='/login') + +@login_bp.route('/', methods=['GET', 'POST']) +def login(): + if session.get('loggedin'): + return redirect(url_for('index.index')) + + username = "" + username_err = "" + password_err = "" + login_err = "" + + if request.method == 'POST': + username = request.form.get('username', '').strip() + password = request.form.get('password', '').strip() + + if not username: + username_err = "Please enter username." + if not password: + password_err = "Please enter your password." + + if not username_err and not password_err: + # Admin bypass (same as before) but don't do this in production! + if False: # username == "adm" and password == "dont add this in please": + session['loggedin'] = True + session['id'] = -1 + session['username'] = "Admin" + return redirect(url_for('index.index')) + + # Query User via SQLAlchemy + user = User.query.filter_by(username=username).first() + + if user: + # Here you need to store hashed passwords somewhere + # Your User model doesn't have a password field yet, so let's assume: + # You should add it like: password = db.Column(db.String(128), nullable=False) + # For now, assuming you have a password attribute + if hasattr(user, 'password') and check_password_hash(user.password, password): + session['loggedin'] = True + session['id'] = user.id + session['username'] = user.username + return redirect(url_for('index.index')) + else: + login_err = "Invalid username or password." + else: + login_err = "Invalid username or password." + + return render_template('login.html', + username=username, + username_err=username_err, + password_err=password_err, + login_err=login_err) diff --git a/pages/logout.py b/pages/logout.py new file mode 100644 index 0000000..9c961a0 --- /dev/null +++ b/pages/logout.py @@ -0,0 +1,8 @@ +from flask import Blueprint, session, redirect, url_for + +logout_bp = Blueprint('logout', __name__) + +@logout_bp.route('/logout') +def logout(): + session.clear() # Clear all session data + return redirect(url_for('login.login')) # Redirect to login page diff --git a/pages/profile.py b/pages/profile.py new file mode 100644 index 0000000..b4a94e2 --- /dev/null +++ b/pages/profile.py @@ -0,0 +1,28 @@ +from flask import Blueprint, render_template, session, redirect, url_for +from models import User, Finding + +profile_bp = Blueprint('profile', __name__, url_prefix='/profile') + +@profile_bp.route('/') +def my_findings(): + # Check if user is logged in + if not session.get('loggedin'): + return redirect(url_for('login.login')) + + user_id = session.get('id') + user = User.query.get(user_id) + if not user: + return redirect(url_for('login.login')) + + # Get all findings by this user, exclude content_preview + findings = Finding.query.filter_by(found_by=user_id).all() + + return render_template('profile.html', user=user, findings=findings) + +@profile_bp.route('/get/', methods=['GET']) +def view_profile(id): + user = User.query.get(id) + if not user: + return "User not found. Please try again later.", + findings = Finding.query.filter_by(found_by=id).all() + return render_template('view_profile.html', user=user, findings=findings) \ No newline at end of file diff --git a/pages/register.py b/pages/register.py new file mode 100644 index 0000000..312228f --- /dev/null +++ b/pages/register.py @@ -0,0 +1,68 @@ +import re +from datetime import datetime +from flask import Blueprint, render_template, request, redirect, url_for, flash +from werkzeug.security import generate_password_hash +from models import db, User + +register_bp = Blueprint('register', __name__, url_prefix='/register') + +@register_bp.route('/', methods=['GET', 'POST']) +def register(): + username = '' + password = '' + confirm_password = '' + username_err = '' + password_err = '' + confirm_password_err = '' + + if request.method == 'POST': + username = request.form.get('username', '').strip() + password = request.form.get('password', '').strip() + confirm_password = request.form.get('confirm_password', '').strip() + + # Validate username + if not username: + username_err = "Please enter a username." + elif not re.match(r'^[a-zA-Z0-9_]+$', username): + username_err = "Username can only contain letters, numbers, and underscores." + else: + # Check if username already exists + if User.query.filter_by(username=username).first(): + username_err = "This username is already taken." + + # Validate password + if not password: + password_err = "Please enter a password." + elif len(password) < 6: + password_err = "Password must have at least 6 characters." + + # Validate confirm password + if not confirm_password: + confirm_password_err = "Please confirm password." + elif password != confirm_password: + confirm_password_err = "Password did not match." + + # If no errors, insert new user + if not username_err and not password_err and not confirm_password_err: + hashed_password = generate_password_hash(password) + new_user = User( + username=username, + password=hashed_password, + register_time=datetime.utcnow() + ) + try: + db.session.add(new_user) + db.session.commit() + flash("Registration successful! Please login.", "success") + return redirect(url_for('login.login')) + except Exception as e: + db.session.rollback() + flash("Oops! Something went wrong. Please try again.", "danger") + + return render_template('register.html', + username=username, + password=password, + confirm_password=confirm_password, + username_err=username_err, + password_err=password_err, + confirm_password_err=confirm_password_err) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a210ec9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +flask +flask-cors +flask_sqlalchemy +requests +beautifulsoup4 +lxml +python-dotenv \ No newline at end of file diff --git a/secret.key b/secret.key new file mode 100644 index 0000000..fe4743a --- /dev/null +++ b/secret.key @@ -0,0 +1 @@ +aea68160bc8c772c266e5dbdb40b2023 \ No newline at end of file diff --git a/static/main.css b/static/main.css new file mode 100644 index 0000000..d037f5b --- /dev/null +++ b/static/main.css @@ -0,0 +1,137 @@ +body { + margin: 0; + font-family: Arial, Helvetica, sans-serif; +} + +/* Navigation styles you already have */ +.topnav { + overflow: hidden; + background-color: #333; +} + +.topnav a { + float: left; + color: #f2f2f2; + text-align: center; + padding: 14px 16px; + text-decoration: none; + font-size: 17px; +} + +.topnav a:hover { + background-color: #ddd; + color: black; +} + +.topnav a.active { + background-color: #04AA6D; + color: white; +} + +/* Form container wrapper */ +.wrapper { + max-width: 480px; + margin: 30px auto; + padding: 20px; + background-color: #f8f9fa; + border-radius: 6px; + box-shadow: 0 0 8px rgba(0,0,0,0.1); +} + +/* Form group spacing */ +.form-group { + margin-bottom: 1rem; +} + +/* Form labels */ +label { + display: block; + margin-bottom: 0.5rem; + font-weight: 600; + color: #333; +} + +/* Form inputs */ +input[type="text"], +input[type="password"], +input[type="email"], +textarea, +select { + width: 100%; + padding: 8px 12px; + border: 1px solid #ced4da; + border-radius: 4px; + font-size: 1rem; + transition: border-color 0.2s ease-in-out; + box-sizing: border-box; +} + +input[type="text"]:focus, +input[type="password"]:focus, +input[type="email"]:focus, +textarea:focus, +select:focus { + border-color: #04AA6D; + outline: none; + box-shadow: 0 0 4px #04AA6D; +} + +/* Buttons */ +button.btn, input[type="submit"], input[type="reset"] { + padding: 10px 18px; + font-size: 1rem; + border-radius: 4px; + border: none; + cursor: pointer; + color: white; + background-color: #04AA6D; + transition: background-color 0.2s ease-in-out; +} + +button.btn:hover, +input[type="submit"]:hover, +input[type="reset"]:hover { + background-color: #039a5b; +} + +/* Secondary button */ +input[type="reset"] { + background-color: #6c757d; + margin-left: 8px; +} + +input[type="reset"]:hover { + background-color: #565e64; +} + +/* Form feedback text */ +.form-text { + font-size: 0.875rem; + color: #6c757d; +} + +/* Flash message styling */ +.flash-message { + margin: 1rem 0; + padding: 12px 20px; + border-radius: 4px; + font-weight: 600; +} + +.flash-message.success { + background-color: #d4edda; + color: #155724; + border: 1px solid #c3e6cb; +} + +.flash-message.danger { + background-color: #f8d7da; + color: #721c24; + border: 1px solid #f5c6cb; +} + +.flash-message.warning { + background-color: #fff3cd; + color: #856404; + border: 1px solid #ffeeba; +} diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..e23652b --- /dev/null +++ b/templates/base.html @@ -0,0 +1,31 @@ + + + + + + {% block title %}My Site{% endblock %} + + + + + {% include 'topbar.html' %} + + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
+ {% for category, message in messages %} +
{{ message }}
+ {% endfor %} +
+ {% endif %} + {% endwith %} + +
+ {% block content %} + + {% endblock %} +
+ + + + \ No newline at end of file diff --git a/templates/create_finding.html b/templates/create_finding.html new file mode 100644 index 0000000..9282d46 --- /dev/null +++ b/templates/create_finding.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} + +{% block title %}Create Finding{% endblock %} + +{% block content %} +

Create a New Finding

+ +
+
+ + + Example: some/path/here +
+ +
+ + +
+ + +
+{% endblock %} diff --git a/templates/finding_detail.html b/templates/finding_detail.html new file mode 100644 index 0000000..0ffb521 --- /dev/null +++ b/templates/finding_detail.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block title %}Finding: {{ finding.title }}{% endblock %} + +{% block content %} +

{{ finding.title }}

+

Path: {{ finding.path }}

+

Found by: {{ user.username }}

+

Found on: {{ finding.find_time.strftime('%Y-%m-%d %H:%M') }}

+ +
+

Content Preview

+
{{ finding.content_preview or 'No preview available.' }}
+{% endblock %} diff --git a/templates/findings.html b/templates/findings.html new file mode 100644 index 0000000..e69de29 diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 0000000..cf954e3 --- /dev/null +++ b/templates/home.html @@ -0,0 +1,18 @@ + +{% extends "base.html" %} + +{% block title %}Home - LAMINAX.ORG ARG findings{% endblock %} + +{% block content %} +

Welcome to ARG findings!

+

This site is dedicated to sharing the ARG findings at laminax.org

+ +
+

Latest Users

+
    + {% for user in users %} +
  • {{ user.username }}
  • + {% endfor %} +
+
+{% endblock %} diff --git a/templates/latest_findings.html b/templates/latest_findings.html new file mode 100644 index 0000000..da21a92 --- /dev/null +++ b/templates/latest_findings.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} + +{% block title %}Latest Findings{% endblock %} + +{% block content %} +

Latest Findings

+ +{% if findings %} +
    + {% for f in findings %} +
  • + {{ f.title }} + by {{ user_map[f.found_by].username if f.found_by in user_map else 'Unknown' }} + — {{ f.find_time.strftime('%Y-%m-%d %H:%M') }} +
  • + {% endfor %} +
+{% else %} +

No findings yet.

+{% endif %} +{% endblock %} diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..e17c129 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,41 @@ +{% extends "base.html" %} + +{% block title %}Login{% endblock %} + +{% block content %} +
+

Login

+

Please fill in your credentials to login.

+ + {% if login_err %} +
{{ login_err }}
+ {% endif %} + +
+
+ + + {% if username_err %} +
{{ username_err }}
+ {% endif %} +
+ +
+ + + {% if password_err %} +
{{ password_err }}
+ {% endif %} +
+ +
+ +
+ +

Don't have an account? Sign up now.

+
+
+{% endblock %} diff --git a/templates/profile.html b/templates/profile.html new file mode 100644 index 0000000..51dbbce --- /dev/null +++ b/templates/profile.html @@ -0,0 +1,23 @@ +{% extends "base.html" %} + +{% block title %}My Findings{% endblock %} + +{% block content %} +

My Findings

+

Welcome, {{ user.username }}!

+ +{% if findings %} +
    + {% for finding in findings %} +
  • + {{ finding.title }} — + Path: {{ finding.path }} — + Found on: {{ finding.find_time.strftime('%Y-%m-%d %H:%M') }} +
  • + {% endfor %} +
+{% else %} +

You have no findings yet.

+{% endif %} + +{% endblock %} diff --git a/templates/register.html b/templates/register.html new file mode 100644 index 0000000..c78df9e --- /dev/null +++ b/templates/register.html @@ -0,0 +1,49 @@ +{% extends "base.html" %} + +{% block title %}Sign Up{% endblock %} + +{% block content %} +
+

Sign Up

+

Please fill this form to create an account.

+ +
+
+ + + {% if username_err %} +
{{ username_err }}
+ {% endif %} +
+ +
+ + + {% if password_err %} +
{{ password_err }}
+ {% endif %} +
+ +
+ + + {% if confirm_password_err %} +
{{ confirm_password_err }}
+ {% endif %} +
+ +
+ + +
+ +

Already have an account? Login here.

+
+
+{% endblock %} diff --git a/templates/topbar.html b/templates/topbar.html new file mode 100644 index 0000000..e4c06c8 --- /dev/null +++ b/templates/topbar.html @@ -0,0 +1,15 @@ +
+ Home + Latest findings + Main site + + {% if session.get('loggedin') %} + Log out + My Findings + Add Finding + My Profile + {% else %} + Login + Register + {% endif %} +
\ No newline at end of file diff --git a/templates/view_profile.html b/templates/view_profile.html new file mode 100644 index 0000000..378fd09 --- /dev/null +++ b/templates/view_profile.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} + +{% block title %}My Findings{% endblock %} + +{% block content %} +

{{ user.username }}'s profile:

+

Their Findings

+{% if findings %} +
    + {% for finding in findings %} +
  • + {{ finding.title }} — + Path: {{ finding.path }} — + Found on: {{ finding.find_time.strftime('%Y-%m-%d %H:%M') }} +
  • + {% endfor %} +
+{% else %} +

You have no findings yet.

+{% endif %} + +{% endblock %}