initial commit
commit
8e7cefdab1
@ -0,0 +1,2 @@
|
||||
.DS_STORE
|
||||
venv/
|
@ -0,0 +1,19 @@
|
||||
# Imports
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
# Database
|
||||
database = SQLAlchemy()
|
||||
|
||||
# User
|
||||
class User ( database.Model ):
|
||||
id = database.Column(database.Integer, primary_key = True)
|
||||
username = database.Column(database.String, nullable = False, unique = True)
|
||||
password = database.Column(database.String, nullable = False)
|
||||
memes = database.relationship("Meme", lazy = "dynamic")
|
||||
|
||||
# Meme
|
||||
class Meme ( database.Model ):
|
||||
id = database.Column(database.Integer, primary_key = True)
|
||||
user = database.Column(database.String, database.ForeignKey("user.username"), nullable = False)
|
||||
title = database.Column(database.String, nullable = False)
|
||||
image = database.Column(database.String, nullable = False, unique = True)
|
@ -0,0 +1,132 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
from flask import Flask, render_template, request, redirect, session
|
||||
from flask_bcrypt import Bcrypt
|
||||
from database import database, User, Meme
|
||||
import os
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
app.secret_key = "secret"
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///" + os.path.join(os.path.abspath(os.path.dirname(__file__)), 'db.sqlite')
|
||||
database.init_app(app)
|
||||
bcrypt = Bcrypt(app)
|
||||
|
||||
|
||||
with app.app_context():
|
||||
database.create_all()
|
||||
|
||||
|
||||
|
||||
def usernameValid ( username ):
|
||||
# Allowed Characters
|
||||
characters = "abcdefghijklmnopqrstuvwxyzABDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"
|
||||
|
||||
if len(username) < 4:
|
||||
return False
|
||||
|
||||
for char in username:
|
||||
if not char in characters:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def passwordValid ( password ):
|
||||
if len(password) < 1:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def root ():
|
||||
memes = database.session.execute(database.Select(Meme).order_by(Meme.id.desc())).all()
|
||||
if session.get("username"):
|
||||
return render_template("index.html", memes = memes, loggedIn = True)
|
||||
else:
|
||||
return render_template("index.html", memes = memes, loggedIn = False)
|
||||
|
||||
|
||||
@app.route("/sign-up", methods = [ "GET", "POST"])
|
||||
def signUp ():
|
||||
if session.get("username"):
|
||||
return redirect("/")
|
||||
if request.method == "GET":
|
||||
return render_template("sign-up.html", loggedIn = False)
|
||||
else:
|
||||
if usernameValid(request.form.get("username")) and passwordValid(request.form.get("password")) and request.form.get("password") == request.form.get("repeatPassword"):
|
||||
users = database.session.execute(database.select(User).filter_by(username=request.form.get("username"))).first()
|
||||
if users == None:
|
||||
database.session.add(User(username = request.form.get("username"), password = bcrypt.generate_password_hash(request.form.get("password"))))
|
||||
database.session.commit()
|
||||
return redirect("/sign-in")
|
||||
return "Username taken"
|
||||
else:
|
||||
return "Invalid username and / or password"
|
||||
|
||||
|
||||
@app.route("/sign-in", methods = [ "GET", "POST"])
|
||||
def signIn ():
|
||||
if session.get("username"):
|
||||
return redirect("/")
|
||||
if request.method == "GET":
|
||||
return render_template("sign-in.html", loggedIn = False)
|
||||
else:
|
||||
if usernameValid(request.form.get("username")) and passwordValid(request.form.get("password")):
|
||||
print("val")
|
||||
user = database.session.execute(database.select(User).filter_by(username=request.form.get("username"))).first()
|
||||
if user != None:
|
||||
if bcrypt.check_password_hash(user[0].password, request.form.get("password")):
|
||||
session["username"] = request.form.get("username")
|
||||
return redirect("/")
|
||||
return render_template("sign-in.html", loggedIn = False)
|
||||
|
||||
@app.route("/sign-out")
|
||||
def signOut():
|
||||
if session.get("username"):
|
||||
session.pop("username")
|
||||
return redirect("/")
|
||||
|
||||
|
||||
@app.route("/upload", methods = ["GET", "POST"])
|
||||
def upload ():
|
||||
if session.get("username") == None:
|
||||
return redirect("/")
|
||||
if request.method == "GET":
|
||||
return render_template("upload.html", loggedIn = True)
|
||||
else:
|
||||
|
||||
if request.files.get("image").filename.split(".")[-1] in ["png", "jpg", "jpeg", "webp"]:
|
||||
|
||||
meme = database.session.execute(database.select(Meme).order_by(Meme.id.desc())).first()
|
||||
if meme == None:
|
||||
image_num = 1
|
||||
else:
|
||||
image_num = meme[0].id + 1
|
||||
|
||||
database.session.add(Meme( user = session.get("username") ,title = request.form.get("title"), image = str(image_num) + ".png"))
|
||||
database.session.commit()
|
||||
request.files.get("image").save(os.path.join("./static/memes", str(image_num) + ".png"))
|
||||
|
||||
|
||||
return redirect("/upload")
|
||||
|
||||
return "oof"
|
||||
|
||||
|
||||
@app.route("/user/<name>")
|
||||
def user ( name ):
|
||||
user = database.session.execute(database.select(User).filter_by(username = name )).first()
|
||||
if user != None:
|
||||
memes = database.session.execute(database.select(Meme).filter_by(user=name).order_by(Meme.id.desc())).all()
|
||||
if session.get("username"):
|
||||
return render_template("user.html", username = user[0].username, memes = memes, loggedIn = True)
|
||||
else:
|
||||
return render_template("user.html", username = user[0].username, memes = memes, loggedIn = False)
|
||||
else:
|
||||
return "no user"
|
||||
|
||||
app.run(debug=True)
|
@ -0,0 +1,13 @@
|
||||
bcrypt==4.0.1
|
||||
click==8.1.3
|
||||
Flask==2.2.3
|
||||
Flask-Bcrypt==1.0.1
|
||||
Flask-SQLAlchemy==3.0.3
|
||||
importlib-metadata==6.6.0
|
||||
itsdangerous==2.1.2
|
||||
Jinja2==3.1.2
|
||||
MarkupSafe==2.1.2
|
||||
SQLAlchemy==2.0.10
|
||||
typing_extensions==4.5.0
|
||||
Werkzeug==2.2.3
|
||||
zipp==3.15.0
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 555 KiB |
@ -0,0 +1,143 @@
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
body {
|
||||
background: rgb(33, 33, 33);
|
||||
}
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: Writer-Regular;
|
||||
src: url(/static/Writer-Regular.ttf);
|
||||
}
|
||||
|
||||
nav {
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgb(17, 17, 17);
|
||||
}
|
||||
|
||||
.navLogo {
|
||||
width: 30%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.navLinks {
|
||||
width: 30%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
font-family: Writer-Regular;
|
||||
font-size: 16px;
|
||||
|
||||
}
|
||||
.navLinks a {
|
||||
margin: 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.memes {
|
||||
width: 100vw;
|
||||
height: fit-content;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
|
||||
background: rgb(33, 33, 33);
|
||||
}
|
||||
|
||||
|
||||
.meme {
|
||||
width: 90%;
|
||||
max-width: 800px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
|
||||
background: linear-gradient(135deg, rgba(50, 50, 50, 0.4), rgba(55, 55, 55, 0.4));
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border-radius: 20px;
|
||||
border:1px solid rgba(255, 255, 255, 0.18);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||
|
||||
transition: linear 0.2s;
|
||||
margin: 20px;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
font-family: Writer-Regular;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.meme h1 {
|
||||
color: white;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.meme h1 a {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.meme img {
|
||||
aspect-ratio: 1 / 1;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1800px) {
|
||||
nav {
|
||||
flex-direction: column;
|
||||
}
|
||||
.navLogo, .navLinks {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
.navLinks {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.userTitle {
|
||||
width: 100vw;
|
||||
background: rgb(33, 33, 33);
|
||||
color: white;
|
||||
font-family: Writer-Regular;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.uploadFormDiv {
|
||||
padding: 10px;
|
||||
background: rgb(33, 33, 33);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.uploadFormDiv form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: white;
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.uploadFormDiv form input {
|
||||
padding: 10px 20px;
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<link rel="stylesheet" href="/static/main.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<nav>
|
||||
<div class = "navLogo">
|
||||
<img src="/static/logo.png" width = "300px">
|
||||
</div>
|
||||
|
||||
{% if loggedIn == True %}
|
||||
<div class = "navLinks">
|
||||
<a href="/">Home</a>
|
||||
<a href="/upload">Upload</a>
|
||||
<a href="/sign-out">Sign Out</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class = "navLinks">
|
||||
<a href="/">Home</a>
|
||||
<a href="/sign-in">Sign In</a>
|
||||
<a href="/sign-up">Sign Up</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</nav>
|
||||
|
||||
|
||||
<div class = "memes">
|
||||
|
||||
{% for meme in memes %}
|
||||
<div class = "meme">
|
||||
<h1>{{ meme[0].title }} by <a href="/user/{{ meme[0].user}}">{{ meme[0].user}}</a></h1>
|
||||
<img src="/static/memes/{{ meme[0].image }}" class = "memeImage">
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
const items = document.querySelectorAll(".meme")
|
||||
|
||||
console.log(items)
|
||||
items.forEach( item => {
|
||||
item.addEventListener("mousemove", event => {
|
||||
console.log(item)
|
||||
item.style.transform = `perspective(500px) scale(1.02) rotateX(${ - 20 * (( item.getBoundingClientRect().bottom - event.clientY - item.clientHeight / 2 ) / item.clientHeight ) / 5 }deg) rotateY(${ 20 * (( item.getBoundingClientRect().right - event.clientX - item.clientWidth / 2) / item.clientWidth ) / 5 }deg)`;
|
||||
})
|
||||
|
||||
item.addEventListener("mouseout", event => {
|
||||
item.style.transform = "perspective(500px) scale(1) rotateX(0) rotateY(0)";
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<link rel="stylesheet" href="/static/main.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<div class = "navLogo">
|
||||
<img src="/static/logo.png" width = "300px">
|
||||
</div>
|
||||
|
||||
{% if loggedIn == True %}
|
||||
<div class = "navLinks">
|
||||
<a href="/">Home</a>
|
||||
<a href="/upload">Upload</a>
|
||||
<a href="/sign-out">Sign Out</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class = "navLinks">
|
||||
<a href="/">Home</a>
|
||||
<a href="/sign-in">Sign In</a>
|
||||
<a href="/sign-up">Sign Up</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</nav>
|
||||
|
||||
<h1 class = "userTitle">Sign In</h1>
|
||||
|
||||
<div class = "uploadFormDiv">
|
||||
<form action="/sign-in" method = "POST">
|
||||
<input type="text" name = "username" placeholder="Username">
|
||||
<input type="password" name = "password" placeholder="Password">
|
||||
<input type="submit" value="Sign Up">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<link rel="stylesheet" href="/static/main.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<div class = "navLogo">
|
||||
<img src="/static/logo.png" width = "300px">
|
||||
</div>
|
||||
|
||||
{% if loggedIn == True %}
|
||||
<div class = "navLinks">
|
||||
<a href="/">Home</a>
|
||||
<a href="/upload">Upload</a>
|
||||
<a href="/sign-out">Sign Out</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class = "navLinks">
|
||||
<a href="/">Home</a>
|
||||
<a href="/sign-in">Sign In</a>
|
||||
<a href="/sign-up">Sign Up</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</nav>
|
||||
|
||||
<h1 class = "userTitle">Sign Up</h1>
|
||||
|
||||
<div class = "uploadFormDiv">
|
||||
<form action="/sign-up" method = "POST">
|
||||
<input type="text" name = "username" placeholder="Username">
|
||||
<input type="password" name = "password" placeholder="Password">
|
||||
<input type="password" name="repeatPassword" placeholder="Repeat Password">
|
||||
<input type="submit" value="Sign Up">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<link rel="stylesheet" href="/static/main.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<div class = "navLogo">
|
||||
<img src="/static/logo.png" width = "300px">
|
||||
</div>
|
||||
|
||||
{% if loggedIn == True %}
|
||||
<div class = "navLinks">
|
||||
<a href="/">Home</a>
|
||||
<a href="/upload">Upload</a>
|
||||
<a href="/sign-out">Sign Out</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class = "navLinks">
|
||||
<a href="/">Home</a>
|
||||
<a href="/sign-in">Sign In</a>
|
||||
<a href="/sign-up">Sign Up</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</nav>
|
||||
|
||||
<h1 class = "userTitle">Upload</h1>
|
||||
|
||||
<div class = "uploadFormDiv">
|
||||
<form action="/upload" method = "POST" enctype="multipart/form-data">
|
||||
<input type="text" name = "title" placeholder="Title">
|
||||
<input type="file" name="image">
|
||||
<input type="submit" value="Upload">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
<link rel="stylesheet" href="/static/main.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<div class = "navLogo">
|
||||
<img src="/static/logo.png" width = "300px">
|
||||
</div>
|
||||
|
||||
{% if loggedIn == True %}
|
||||
<div class = "navLinks">
|
||||
<a href="/">Home</a>
|
||||
<a href="/upload">Upload</a>
|
||||
<a href="/sign-out">Sign Out</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class = "navLinks">
|
||||
<a href="/">Home</a>
|
||||
<a href="/sign-in">Sign In</a>
|
||||
<a href="/sign-up">Sign Up</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</nav>
|
||||
|
||||
<h1 class = "userTitle">{{ username }}'s Memes</h1>
|
||||
|
||||
<div class = "memes">
|
||||
|
||||
{% for meme in memes %}
|
||||
<div class = "meme">
|
||||
<h1>{{ meme[0].title }}</h1>
|
||||
<img src="/static/memes/{{ meme[0].image }}" class = "memeImage">
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
const items = document.querySelectorAll(".meme")
|
||||
|
||||
console.log(items)
|
||||
items.forEach( item => {
|
||||
item.addEventListener("mousemove", event => {
|
||||
console.log(item)
|
||||
item.style.transform = `perspective(500px) scale(1.02) rotateX(${ - 20 * (( item.getBoundingClientRect().bottom - event.clientY - item.clientHeight / 2 ) / item.clientHeight ) / 5 }deg) rotateY(${ 20 * (( item.getBoundingClientRect().right - event.clientX - item.clientWidth / 2) / item.clientWidth ) / 5 }deg)`;
|
||||
})
|
||||
|
||||
item.addEventListener("mouseout", event => {
|
||||
item.style.transform = "perspective(500px) scale(1) rotateX(0) rotateY(0)";
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue