diff --git a/.gitignore b/.gitignore index 0f0c103..759ae93 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ data -venv -src/static/uploads +src/static/uploads/* diff --git a/README.md b/README.md index b638102..f967df8 100644 --- a/README.md +++ b/README.md @@ -1,31 +1 @@ -# Dating-Website -## Description -Minimal dating website made in python. -It uses flask to render the HTML, saves the data in a MySQL database and also features an authentication method using PGP where the database stores the user's PGP key's fingerprint and uses that to encrypt a message only you decrypt, but successfully decrypting the message you athenticate to your account. -It's also supposed to be very easy to use, currently its still in development but the vision is that you can be enganged on the website right from the start featuring a very powerfull search page and not needing an account to use the website. -This website also does not use JavaScript making it easy to run on any browser. - -## TODO -- making the website responsive -- adding search features -- likes and dislikes -- cool css -- a grid with all of the users on the index page (where the search will also be) -- security audits -- maybe more stuff later... - -## Contributing -If you have suggestions, find bugs, or want to provide code, just open an issue before submiting a PR. I will probably not accept a PR unless I see that it's actually somewhat important, exceptions can be made, but its kinda goofy to write the code before submiting an issue. - -## Running the program -#### Docker/Podman -`docker compose up -d` / `podman-compose up -d` - -### python enviornment -`python -m venv venv` - -### Install the dependencies -`pip install -r requirements.txxt` - -### Run the program -`pyhton src/main.py` +Minimal dating website writen in python diff --git a/src/static/font/font-Bold.ttf b/src/font/font-Bold.ttf similarity index 100% rename from src/static/font/font-Bold.ttf rename to src/font/font-Bold.ttf diff --git a/src/static/font/font.ttf b/src/font/font.ttf similarity index 100% rename from src/static/font/font.ttf rename to src/font/font.ttf diff --git a/src/main.py b/src/main.py index 47f97ef..3a65e68 100644 --- a/src/main.py +++ b/src/main.py @@ -1,15 +1,14 @@ from flask import Flask, render_template, request, redirect, url_for, flash, session from flask_sqlalchemy import SQLAlchemy -from sqlalchemy import text from datetime import date import gnupg import secrets import os from werkzeug.utils import secure_filename -BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # sets the base dir as the diretory where the python file is -UPLOAD_FOLDER = os.path.join(BASE_DIR, "static", "uploads") # joins the directories -os.makedirs(UPLOAD_FOLDER, exist_ok=True) # creates the uploads directorie +# defines where the upload folder is and creates it +UPLOAD_FOLDER = "static/uploads" +os.makedirs(UPLOAD_FOLDER, exist_ok=True) # configures the app app = Flask(__name__) # creates de app @@ -79,22 +78,26 @@ def calculate_age(dob: date) -> int: # saves files to the upload folder and returns their URL def save_files(username: str, profile_file, pictures_files): + # creates a path for the user inside the upload forlder user_folder = os.path.join(app.config['UPLOAD_FOLDER'], username) os.makedirs(user_folder, exist_ok=True) + # prevents unsafe characters to be used in the filename profile_filename = secure_filename(profile_file.filename) profile_path = os.path.join(user_folder, profile_filename) + + # saves the profile picture to the path profile_file.save(profile_path) + profile_url = f"/{profile_path.replace(os.sep, '/')}" - profile_url = f"/static/uploads/{username}/{profile_filename}" - + # saves all of the other pictures pictures_urls = [] for pic in pictures_files: if pic.filename: filename = secure_filename(pic.filename) path = os.path.join(user_folder, filename) pic.save(path) - pictures_urls.append(f"/static/uploads/{username}/{filename}") + pictures_urls.append(f"/{path.replace(os.sep, '/')}") return profile_url, pictures_urls @@ -116,58 +119,8 @@ def pgp_encrypt_and_import(pgp_key: str, message: str): @app.route("/") def home(): - query = User.query + return render_template("index.html") - country = request.args.get("country") - city = request.args.get("city") - sex = request.args.get("sex") - age_min = request.args.get("age_min") - age_max = request.args.get("age_max") - race = request.args.get("race") - likes = request.args.get("likes") - dislikes = request.args.get("dislikes") - - if country: - query = query.filter(User.country.ilike(f"%{country}%")) - if city: - query = query.filter(User.city.ilike(f"%{city}%")) - if sex: - query = query.filter(User.sex==sex) - if race: - query = query.filter(User.race.ilike(f"%{race}%")) - - today = date.today() - if age_min: - try: - min_age = int(age_min) - dob_max = date(today.year - min_age, today.month, today.day) - query = query.filter(User.date_of_birth <= dob_max) - except ValueError: - pass - if age_max: - try: - max_age = int(age_max) - dob_min = date(today.year - max_age - 1, today.month, today.day) - query = query.filter(User.date_of_birth >= dob_min) - except ValueError: - pass - - if likes: - likes_list = [x.strip().lower() for x in likes.split(",") if x.strip()] - for like in likes_list: - query = query.filter( - text(f"JSON_CONTAINS(likes, '\"{like}\"')") - ) - - if dislikes: - dislikes_list = [x.strip().lower() for x in dislikes.split(",") if x.strip()] - for dislike in dislikes_list: - query = query.filter( - text(f"JSON_CONTAINS(dislikes, '\"{dislike}\"')") - ) - - users = query.all() - return render_template("index.html", users=users, date=date) @app.route("/register", methods=["GET", "POST"]) def register(): @@ -175,39 +128,9 @@ def register(): # collect data to a dictionary data = {key: request.form.get(key) for key in [ "username","pgp","firstname","lastname","sex","date_of_birth","country","xmpp", - "email","phone","city","height","weight","race" + "email","phone","city","height","weight","race","prefered_age_range" ]} - min_age = request.form.get("preferred_age_min") - max_age = request.form.get("preferred_age_max") - - if min_age and max_age: - try: - min_age = int(min_age) - max_age = int(max_age) - - if min_age < 18 or max_age < 18: - flash("Minimum age is 18.") - return redirect(url_for("register")) - - if min_age > max_age: - flash("Minimum age cannot be greater than maximum age.") - return redirect(url_for("register")) - - data["prefered_age_range"] = f"{min_age}-{max_age}" - - except ValueError: - flash("Invalid age range.") - return redirect(url_for("register")) - else: - data["prefered_age_range"] = None - - likes_raw = request.form.get("likes", "") - dislikes_raw = request.form.get("dislikes", "") - - data["likes"] = list(set(x.strip().lower() for x in likes_raw.split(",") if x.strip())) - data["dislikes"] = list(set(x.strip().lower() for x in dislikes_raw.split(",") if x.strip())) - # required fields required_fields = ["username","pgp","firstname","lastname","sex","date_of_birth","country","xmpp"] if not all(data[f] for f in required_fields): @@ -246,30 +169,19 @@ def register(): # creates a random string random_string = secrets.token_hex(16) - # uses the string to create the message that wll be encrypted challenge_phrase = f"this is the unencrypted string: {random_string}" - # encrypts message fingerprint, encrypted_msg = pgp_encrypt_and_import(data["pgp"], challenge_phrase) - - # checks fingerprint if not fingerprint or not encrypted_msg: flash("Invalid PGP key or encryption failed.") return redirect(url_for("register")) - print(fingerprint) # creates a temporary session used to verify the user - session["pending_user"] = { - **data, - "profile_url": profile_url, - "pictures_urls": pictures_urls, - "fingerprint": fingerprint - } - - session['pgp_expected_phrase'] = challenge_phrase + session["pending_user"] = {**data, "profile_url": profile_url, "pictures_urls": pictures_urls} + session["pgp_expected_phrase"] = challenge_phrase # renders the verification page return render_template("verify.html", encrypted_message=encrypted_msg) @@ -279,23 +191,18 @@ def register(): @app.route("/verify", methods=["POST"]) def verify(): - # retrieve user data from the session - data = session.get("pending_user") - - fingerprint = data.get("fingerprint") - # retrieve the phrase from the session expected_phrase = session.get("pgp_expected_phrase") - + # retrieve user data from the session + data = session.get("pending_user") # check to see if data exists if not data or not expected_phrase: flash("Session expired.") return redirect(url_for("register")) - # get the decrypted message from form + # get the decrypted message submitted = request.form.get("decrypted_message") - # check to see if submission was empty if not submitted: flash("You must paste the decrypted message.") @@ -327,8 +234,6 @@ def verify(): height=float(data["height"]) if data.get("height") else None, weight=int(data["weight"]) if data.get("weight") else None, race=data.get("race") or None, - likes=data.get("likes") or [], - dislikes=data.get("dislikes") or [], prefered_age_range=data.get("prefered_age_range") or None, is_verified=True ) @@ -339,9 +244,9 @@ def verify(): # creates login session session['user_id'] = new_user.id session['username'] = new_user.username - # remove temporary session session.pop("pending_user", None) + session.pop("pgp_expected_phrase", None) flash("PGP verification successful! Account created.") return redirect(url_for("home")) diff --git a/src/static/drip.css b/src/static/drip.css index e48c8aa..3f25630 100644 --- a/src/static/drip.css +++ b/src/static/drip.css @@ -1,30 +1,28 @@ @font-face { font-family: 'font'; - src: url('/static/font/font.ttf') format('truetype'); + src: url('/font/font.ttf') format('truetype'); font-weight: normal; font-style: normal; font-display: swap; } @font-face { font-family: 'font'; - src: url('/static/font/font-Bold.ttf') format('truetype'); + src: url('/font/font-Bold.ttf') format('truetype'); font-weight: bold; font-style: normal; font-display: swap; } body { - background: #FFE0F4; - color: #FF00AA; - text-shadow: 0px 0px 5px rgba(255, 0, 170, 0.8); - padding: 5px; - max-width: 75%; + background: #101210; + color: #e0e0e0; + text-shadow: 2px 2px 5px rgba(0, 0, 0, 0.8); + max-width: 800px; margin: auto; font-family: font; font-weight: normal; line-height: 1.2rem; word-wrap: break-word; - font-size: 22px; } footer { @@ -44,55 +42,44 @@ strong, b { font-weight: bold; } -section { - margin-top: 32px; - background: #fff; - padding: 5px; - border: medium; - border-color: #FF00AA; - border-radius: 5px; - border-style: dashed; -} - h1 { - color: #FF00AA; + color: #00ff00; text-decoration: underline yellow; text-align: center; } h2 { - color: #FF00AA; + color: #00ff00; } h3 { - color: #FF00AA; + color: #00ff00; } a { - color: #FF699B; + color: #ffff00; } a:hover { color: #ffffff; } -table { - width: 100%; - border-collapse: collapse; +summary { + color: #008800; + background: #101210; } -th, td { - border: 1px solid #ddd; - padding: 8px; - text-align: left; +details { + background: #222; } -th { - background-color: #FF00AA; - color: white; +summary:hover { + color: #fff; + cursor: pointer; } -tr:nth-child(even) { - background-color: #FF00AA; +.service { + padding: 0.5rem; + border: solid thin #ffff00; } diff --git a/src/templates/index.html b/src/templates/index.html index 575c8e6..8bd4d52 100644 --- a/src/templates/index.html +++ b/src/templates/index.html @@ -1,47 +1,15 @@ {% extends "page.html" %} {% block content %} -
No users found.
- {% endif %} -Page text
+Page text
+Page text
+Page text
+Page text
+Page text
+Page text
+Page text
+Page text
+Page text
{% endblock %} diff --git a/src/templates/login.html b/src/templates/login.html index 0fe3b7b..f0b5116 100644 --- a/src/templates/login.html +++ b/src/templates/login.html @@ -2,34 +2,15 @@ {% block content %}Enter your username and PGP public key to receive a verification challenge.
+Enter your username and PGP public key to receive a challenge.
- - -{% with messages = get_flashed_messages() %} - {% if messages %} -Age: {{ (date.today() - user.date_of_birth).days // 365 }}
-Sex: {{ user.sex|capitalize }}
-Sex: {{ user.sex }}
+Date of Birth: {{ user.date_of_birth }}
+Age: {{ (date.today() - user.date_of_birth).days // 365 }}
+Race: {{ user.race or 'Not specified' }}
-Date of Birth: {{ user.date_of_birth }}
-Race: {{ user.race or 'Not specified' }}
-Country: {{ user.country }}
+City: {{ user.city or 'Not specified' }}
+Height: {{ user.height or 'Not specified' }} m
+Weight: {{ user.weight or 'Not specified' }} kg
-Country: {{ user.country }}
-City: {{ user.city or 'Not specified' }}
-Height: - {% if user.height %} - {{ user.height }} m - {% else %} - Not specified - {% endif %} -
- -Weight: - {% if user.weight %} - {{ user.weight }} kg - {% else %} - Not specified - {% endif %} -
-Preferred Age Range: - {{ user.prefered_age_range or 'Not specified' }} -
- -Likes:
- {% if user.likes %} -Not specified
- {% endif %} - -Dislikes:
- {% if user.dislikes %} -Not specified
- {% endif %} -XMPP: {{ user.xmpp }}
-Email: {{ user.email or 'Not specified' }}
-Phone: {{ user.phone or 'Not specified' }}
-Preferred Age Range: {{ user.prefered_age_range or 'Not specified' }}
+Likes: {{ user.likes | join(', ') if user.likes else 'Not specified' }}
+Dislikes: {{ user.dislikes | join(', ') if user.dislikes else 'Not specified' }}
+XMPP: {{ user.xmpp }}
+Email: {{ user.email or 'Not specified' }}
+Phone: {{ user.phone or 'Not specified' }}
{% endblock %}