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 %} -

Discover Users

- -
-

Search Users

-
- - - - - - - - - - - - - -
-
- -
-

Users

- {% if users %} -
- {% for user in users %} -
- - {{ user.username }}
- Age: {{ (date.today() - user.date_of_birth).days // 365 }}
- Country: {{ user.country }} -
-
- {% endfor %} -
- {% else %} -

No users found.

- {% endif %} -
+

Home

+

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 %}

Login

-

Enter your username and PGP public key to receive a verification challenge.

+

Enter your username and PGP public key to receive a challenge.

+
+

-
- Account Verification +
+

-
-

- -
-
- Paste your full public key block -
- -
- +
- - -{% with messages = get_flashed_messages() %} - {% if messages %} - - {% endif %} -{% endwith %} - {% endblock %} diff --git a/src/templates/page.html b/src/templates/page.html index 9cd5589..0f95a01 100644 --- a/src/templates/page.html +++ b/src/templates/page.html @@ -19,7 +19,9 @@ - {% block content %}{% endblock %} + +{% block content %}{% endblock %} + diff --git a/src/templates/register.html b/src/templates/register.html index 70945ae..8cf7492 100644 --- a/src/templates/register.html +++ b/src/templates/register.html @@ -5,117 +5,52 @@
-
- Account (required) +

Identity (required)

+
+
-
-

+

Personal Info (required)

+
+
-
- -
- -
- Personal Info (required) - -
-

- -
-

- -
- -

+
-
- -
+
-
- Pictures +

Profile Picture (required)

+
-
-

+

Other Pictures (optional, multiple)

+
-
- -
- -
- Location - -
- {% for c in countries %} {% endfor %} -

+
+
-
- -
+

Physical Attributes (optional)

+
+
+
-
- Physical Attributes +

Preferences (optional)

+
-
-

+

Contacts (required)

+
+
+

-
-

+ -
- -
- -
- Preferences - -
- -
-
- -
-
- -
-
- Separate with commas

- -
-
- Separate with commas -
- -
- Contacts (required) - -
-

- -
-

- -
- -
- -
-
{% with messages = get_flashed_messages() %} diff --git a/src/templates/user.html b/src/templates/user.html index eae8f80..cfa71bf 100644 --- a/src/templates/user.html +++ b/src/templates/user.html @@ -1,97 +1,37 @@ {% extends "page.html" %} {% block content %} - -
-

{{ user.firstname }} {{ user.lastname }}

- - Profile Picture

- -

Age: {{ (date.today() - user.date_of_birth).days // 365 }}

-

Sex: {{ user.sex|capitalize }}

-
- +

{{ user.firstname }} {{ user.lastname }}

+Profile Picture
{% if user.pictures %} -
-

Gallery

- {% for pic in user.pictures %} - - {% endfor %} -
+

Pictures

+{% for pic in user.pictures %} + +{% endfor %} {% endif %} +

Personal Info

+

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' }}

-
-

Personal Info

-

Date of Birth: {{ user.date_of_birth }}

-

Race: {{ user.race or 'Not specified' }}

-
+

Location

+

Country: {{ user.country }}

+

City: {{ user.city or 'Not specified' }}

+

Physical Attributes

+

Height: {{ user.height or 'Not specified' }} m

+

Weight: {{ user.weight or 'Not specified' }} kg

-
-

Location

-

Country: {{ user.country }}

-

City: {{ user.city or 'Not specified' }}

-
- - -
-

Physical Attributes

-

Height: - {% if user.height %} - {{ user.height }} m - {% else %} - Not specified - {% endif %} -

- -

Weight: - {% if user.weight %} - {{ user.weight }} kg - {% else %} - Not specified - {% endif %} -

-
- - -
-

Preferences

- -

Preferred Age Range: - {{ user.prefered_age_range or 'Not specified' }} -

- -

Likes:

- {% if user.likes %} - - {% else %} -

Not specified

- {% endif %} - -

Dislikes:

- {% if user.dislikes %} - - {% else %} -

Not specified

- {% endif %} -
- - -
-

Contacts

-

XMPP: {{ user.xmpp }}

-

Email: {{ user.email or 'Not specified' }}

-

Phone: {{ user.phone or 'Not specified' }}

-
+

Preferences

+

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' }}

+

Contacts

+

XMPP: {{ user.xmpp }}

+

Email: {{ user.email or 'Not specified' }}

+

Phone: {{ user.phone or 'Not specified' }}

{% endblock %}