Compare commits
8 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d44c1b5f97 | |||
| 6652284dbc | |||
| 069d2b1667 | |||
| 2cc5d78acd | |||
| 96e524d284 | |||
| 8e7e153989 | |||
| ae07e823da | |||
| ccf0729630 |
11 changed files with 116 additions and 146 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,3 +1,5 @@
|
|||
data
|
||||
venv
|
||||
src/static/uploads
|
||||
data
|
||||
src/lovedb.db
|
||||
src/__pycache__
|
||||
|
|
|
|||
19
Dockerfile
Normal file
19
Dockerfile
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
FROM python:3.14.3-alpine3.23
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN apk add gpg
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY src/ ./src/
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
ENV FLASK_APP=src/main.py
|
||||
ENV FLASK_RUN_HOST=0.0.0.0
|
||||
ENV FLASK_ENV=production
|
||||
|
||||
CMD ["flask", "run"]
|
||||
14
README.md
14
README.md
|
|
@ -1,26 +1,14 @@
|
|||
# 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 uses flask to render the HTML, saves the data in a 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`
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
|
||||
services:
|
||||
db:
|
||||
image: mariadb:latest
|
||||
container_name: lovedb
|
||||
restart: always
|
||||
environment:
|
||||
MARIADB_ROOT_PASSWORD: love
|
||||
MARIADB_DATABASE: lovedb
|
||||
MARIADB_USER: love
|
||||
MARIADB_PASSWORD: love
|
||||
web:
|
||||
build: .
|
||||
container_name: dating
|
||||
ports:
|
||||
- "3309:3306"
|
||||
- "5000:5000"
|
||||
volumes:
|
||||
- ./data:/var/lib/mysql
|
||||
- ./src:/app/src
|
||||
- ./data:/app/data
|
||||
environment:
|
||||
- FLASK_APP=src/main.py
|
||||
- FLASK_RUN_HOST=0.0.0.0
|
||||
- FLASK_ENV=development
|
||||
restart: unless-stopped
|
||||
|
|
|
|||
93
plan.md
93
plan.md
|
|
@ -1,93 +0,0 @@
|
|||
# User System Plan
|
||||
|
||||
## 1. Database Schema (`User` model)
|
||||
|
||||
- **Identity & Security**
|
||||
- `username` (unique, required)
|
||||
- `pgp` (unique, required)
|
||||
- `is_verified` (boolean)
|
||||
|
||||
- **Contacts**
|
||||
- `xmpp` (unique, required)
|
||||
- `email` (unique, optional)
|
||||
- `phone` (unique, optional)
|
||||
|
||||
- **Personal Info**
|
||||
- `firstname` (required)
|
||||
- `lastname` (required)
|
||||
- `sex` (`male` / `female`, required)
|
||||
- `date_of_birth` (required)
|
||||
- `race` (optional)
|
||||
|
||||
- **Profile & Media**
|
||||
- `profile_picture` (required)
|
||||
- `pictures` (optional array)
|
||||
|
||||
- **Location**
|
||||
- `country` (required)
|
||||
- `city` (optional)
|
||||
|
||||
- **Physical Attributes**
|
||||
- `height` (optional float)
|
||||
- `weight` (optional int)
|
||||
|
||||
- **Preferences**
|
||||
- `prefered_age_range` (optional)
|
||||
- `likes` (optional array)
|
||||
- `dislikes` (optional array)
|
||||
|
||||
---
|
||||
|
||||
## 2. Registration
|
||||
|
||||
1. **User fills form**
|
||||
- All fields except `id`, `is_verified`.
|
||||
|
||||
2. **Server receives data**
|
||||
- Validate required fields and unique constraints.
|
||||
- Temporarily store as **unverified**.
|
||||
|
||||
3. **PGP Verification**
|
||||
- Server encrypts a message with user's PGP public key.
|
||||
- Show **validation page** with encrypted message.
|
||||
- User decrypts message and submits.
|
||||
- Server validates ownership and sets `is_verified=True`.
|
||||
|
||||
4. **Create Profile Page & Redirect**
|
||||
- Generate user profile page with all info.
|
||||
- Redirect user to main page or search page.
|
||||
|
||||
---
|
||||
|
||||
## 3. Search Page
|
||||
|
||||
- **Display**: Public user profiles as cards
|
||||
- Show `profile_picture`, `firstname`, `lastname`, age, `country`, `city`.
|
||||
|
||||
- **Filters**:
|
||||
- All fields **except** `pgp`, `id`, `username`.
|
||||
- Include boolean checks for presence of `email`, `phone`, `xmpp`.
|
||||
|
||||
- **Profile Click**
|
||||
- Open full profile page with all info, pictures, likes/dislikes.
|
||||
|
||||
---
|
||||
|
||||
## 4. Login Flow
|
||||
|
||||
1. User enters `username` + `PGP key`.
|
||||
2. Server verifies PGP via challenge.
|
||||
3. On success:
|
||||
- User can edit **all fields** execpt `id` and `username`.
|
||||
- User can change pgp key, new pgp key must be verified.
|
||||
|
||||
---
|
||||
|
||||
## 5. Software Stack
|
||||
|
||||
- **Software used**:
|
||||
- Flask (backend framework)
|
||||
- MySQL (Database)
|
||||
- SQLAlchemy (ORM)
|
||||
- python-gnupg (PGP validation)
|
||||
- Flask-WTF (Forms validation)
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
Flask
|
||||
Flask-SQLAlchemy
|
||||
SQLAlchemy
|
||||
PyMySQL
|
||||
python-gnupg
|
||||
|
|
|
|||
BIN
src/__pycache__/main.cpython-314.pyc
Normal file
BIN
src/__pycache__/main.cpython-314.pyc
Normal file
Binary file not shown.
BIN
src/lovedb.db
Normal file
BIN
src/lovedb.db
Normal file
Binary file not shown.
13
src/main.py
13
src/main.py
|
|
@ -13,7 +13,7 @@ os.makedirs(UPLOAD_FOLDER, exist_ok=True) # creates the uploads directorie
|
|||
|
||||
# configures the app
|
||||
app = Flask(__name__) # creates de app
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://love:love@localhost:3309/lovedb' # database connection
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{os.path.join(BASE_DIR, 'lovedb.db')}" # database connection
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # disable track modifications (for better performance)
|
||||
app.config['SECRET_KEY'] = 'random' # sets the secret key used to generate random numbers
|
||||
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER # sets the upload folder
|
||||
|
|
@ -26,7 +26,7 @@ COUNTRIES = [ "Afghanistan","Albania","Algeria","Andorra","Angola","Antigua and
|
|||
"Barbados","Belarus","Belgium","Belize","Benin","Bhutan","Bolivia","Bosnia and Herzegovina",
|
||||
"Botswana","Brazil","Brunei","Bulgaria","Burkina Faso","Burundi","Cabo Verde","Cambodia",
|
||||
"Cameroon","Canada","Central African Republic","Chad","Chile","China","Colombia","Comoros",
|
||||
"Congo (Congo-Brazzaville)","Costa Rica","Croatia","Cuba","Cyprus","Czechia (Czech Republic)",
|
||||
"Congo (Congo-Brazzaville)","Costa Rica","Croatia","Cuba","Cyprus","Czechia (Czech Republic)",
|
||||
"Democratic Republic of the Congo","Denmark","Djibouti","Dominica","Dominican Republic","Ecuador",
|
||||
"Egypt","El Salvador","Equatorial Guinea","Eritrea","Estonia","Eswatini (fmr. Swaziland)",
|
||||
"Ethiopia","Fiji","Finland","France","Gabon","Gambia","Georgia","Germany","Ghana","Greece",
|
||||
|
|
@ -67,6 +67,7 @@ class User(db.Model):
|
|||
prefered_age_range = db.Column(db.String(20), nullable=True)
|
||||
likes = db.Column(db.JSON, nullable=True)
|
||||
dislikes = db.Column(db.JSON, nullable=True)
|
||||
about = db.Column(db.String(4096), nullable=True)
|
||||
xmpp = db.Column(db.String(128), unique=True, nullable=False)
|
||||
email = db.Column(db.String(128), unique=True, nullable=True)
|
||||
phone = db.Column(db.String(20), unique=True, nullable=True)
|
||||
|
|
@ -107,7 +108,7 @@ def pgp_encrypt_and_import(pgp_key: str, message: str):
|
|||
return None, None
|
||||
fingerprint = result.fingerprints[0]
|
||||
# encrypts message to the user's fingerprint
|
||||
encrypted = gpg.encrypt(message, recipients=[fingerprint])
|
||||
encrypted = gpg.encrypt(message, recipients=[fingerprint], always_trust=True)
|
||||
if not encrypted.ok:
|
||||
return fingerprint, None
|
||||
return fingerprint, str(encrypted)
|
||||
|
|
@ -154,10 +155,8 @@ def home():
|
|||
|
||||
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}\"')")
|
||||
)
|
||||
users = query.all()
|
||||
users = [u for u in users if u.likes and all(l in u.likes for l in likes_list)]
|
||||
|
||||
if dislikes:
|
||||
dislikes_list = [x.strip().lower() for x in dislikes.split(",") if x.strip()]
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'font';
|
||||
src: url('/static/font/font-Bold.ttf') format('truetype');
|
||||
|
|
@ -17,12 +18,13 @@ body {
|
|||
background: #FFE0F4;
|
||||
color: #FF00AA;
|
||||
text-shadow: 0px 0px 5px rgba(255, 0, 170, 0.8);
|
||||
padding: 5px;
|
||||
max-width: 75%;
|
||||
margin: auto;
|
||||
padding: 10px;
|
||||
max-width: 900px;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
font-family: font;
|
||||
font-weight: normal;
|
||||
line-height: 1.2rem;
|
||||
line-height: 1.4rem;
|
||||
word-wrap: break-word;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
|
@ -33,11 +35,9 @@ footer {
|
|||
margin-top: auto;
|
||||
}
|
||||
|
||||
main {
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
strong, b {
|
||||
|
|
@ -47,25 +47,18 @@ strong, b {
|
|||
section {
|
||||
margin-top: 32px;
|
||||
background: #fff;
|
||||
padding: 5px;
|
||||
border: medium;
|
||||
border-color: #FF00AA;
|
||||
padding: 10px;
|
||||
border: medium dashed #FF00AA;
|
||||
border-radius: 5px;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
|
||||
h1 {
|
||||
color: #FF00AA;
|
||||
text-decoration: underline yellow;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #FF00AA;
|
||||
}
|
||||
|
||||
h3 {
|
||||
h2, h3 {
|
||||
color: #FF00AA;
|
||||
}
|
||||
|
||||
|
|
@ -80,6 +73,8 @@ a:hover {
|
|||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
overflow-x: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
th, td {
|
||||
|
|
@ -94,5 +89,66 @@ th {
|
|||
}
|
||||
|
||||
tr:nth-child(even) {
|
||||
background-color: #FF00AA;
|
||||
background-color: #FFB3DA;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
body {
|
||||
font-size: 20px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 8px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
|
||||
body {
|
||||
font-size: 18px;
|
||||
padding: 6px;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
section {
|
||||
padding: 8px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
table {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
<head>
|
||||
<title>Dating Website</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='drip.css') }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<header><h1>Dating Website</h1></header>
|
||||
<nav>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue