Upload files to "/"

This commit is contained in:
2026-06-02 23:22:05 +02:00
commit cefbebab33
5 changed files with 319 additions and 0 deletions

92
READ.md Normal file
View File

@@ -0,0 +1,92 @@
#Weatherify
Weatherify is an Flask-SQLAlchemy-applikation that creates Spotify playlists based on the weather of a location the users input.
#Functions
-Register and login system
-Creates playlists based on weatherdata
-Updates playlists with new tracks
-Deletes playlists
#Technices
-Python
-Flask
-SQLAlchemy
-Flask-Login
-HTML / Jinja2
-CSS
-JavaScript
-Spotify API
-OpenWeather API
#Installion
1. Clone project
´´´bash
git clone <repository-url>
cd weatherify
2. Create virtuel enviorment
´´´bash
python -m venv venv
Activate enviorment
´´´bash
.venv\Scripts\activate
3. Install requirements
´´´bash
pip install -requirements.txt
´´´
4. Create one env-file
Create an file which is called .env in projects rootmap with personal API-keys:
´´´env
OPENWEATHER_API_KEY=your_openweather_key
client_id=your_spotify_client_id
client_secret=your_spotify_client_secret
redirect_uri=http://127.0.0.1:5000/callback
SECRET_KEY=your_secret_key
´´´
5. Start application
´´´bash
flask run
´´´
or
´´´bash
python app.py
´´´
6. Open in browser
´´´
http://127.0.0.1:5000
´´´
#Database
Project uses SQLAlchemy.
if database does not exist:
´´´bash
flask shell
´´´
```python
from extensions import db
db.create_all()
```
#Security
-Password are hashed
-Protects from SQL-Injections because of SQLAchemy
-CRSF-protection on forms
-Authentication because of Flask-login

33
app.py Normal file
View File

@@ -0,0 +1,33 @@
from flask import Flask
from flask_migrate import Migrate
from flask_wtf.csrf import CSRFProtect
from dotenv import load_dotenv
from extensions import db, login_manager
from playlist import bp as playlist_bp
from auth import bp as auth_bp
load_dotenv()
def create_app():
app = Flask(__name__)
app.secret_key = "!spotifyweather1234!"
CSRFProtect(app) #aktivera csrf skydd i hela applikationen
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///app.db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db.init_app(app)
Migrate(app, db)
login_manager.init_app(app)
app.register_blueprint(playlist_bp)
app.register_blueprint(auth_bp)
return app
app = create_app()
if __name__ == "__main__":
app.run(debug=True)

23
extensions.py Normal file
View File

@@ -0,0 +1,23 @@
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask_bcrypt import Bcrypt
db = SQLAlchemy()
bcrypt = Bcrypt()
login_manager = LoginManager()
#Protection for user-sessions
login_manager.session_protection = "strong"
#Route users sends to if they are not logged in
login_manager.login_view = "auth.login_form"
#Flash messages incase of unauthrized access
login_manager.login_message = "Login to continue"
login_manager.login_message_category = "error"
@login_manager.user_loader
def load_user(user_id: str):
from models import User
return db.session.get(User, int(user_id))

66
models.py Normal file
View File

@@ -0,0 +1,66 @@
import re
from datetime import datetime
from flask_login import UserMixin
from extensions import db, bcrypt
#Users
class User(UserMixin, db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(100), unique=True, nullable= False)
email = db.Column(db.String(120), unique=True, nullable = False)
password_hash = db.Column(db.String(200), nullable=False)
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
playlist = db.relationship("Playlist", back_populates="user", lazy="dynamic")
def set_password(self, plain_password: str):
if not plain_password or len(plain_password) < 8:
raise ValueError("Password must be at least 8 characters!")
if not re.search(r"[A-Z]", plain_password):
raise ValueError("Password must include one uppercase letter")
if not re.search(r"[\d]", plain_password):
raise ValueError("Password must include one number")
self.password_hash = (
bcrypt.generate_password_hash(
plain_password
).decode("utf-8")
)
def check_password(self, plain_password: str) -> bool:
return bcrypt.check_password_hash(self.password_hash, plain_password)
def __repr__(self):
return f"<User id={self.id} username={self.username!r}>"
#Saves the playlist that the users makes
class Playlist(db.Model):
id= db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100))
description = db.Column(db.String(255))
spotify_playlist_id = db.Column(db.String(120))
spotify_url = db.Column(db.String(300))
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
user = db.relationship("User", backref="playlists")
tracks = db.relationship(
"Tracks",
backref ="playlist",
cascade="all, delete"
)
#Saves every song in the playlist
class Tracks(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255))
artist = db.Column(db.String(255))
playlist_id = db.Column(
db.Integer,
db.ForeignKey("playlist.id")
)

105
routes.py Normal file
View File

@@ -0,0 +1,105 @@
import re
from flask import render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, login_required, current_user
from sqlalchemy.exc import IntegrityError
from extensions import db
from models import User
from . import bp
@bp.get("/register")
def register_form():
if current_user.is_authenticated:
return redirect(url_for("playlist.home"))
return render_template("auth/register.html")
@bp.post("/register")
def register():
username = request.form.get("username", "").strip()
email = request.form.get("email", "").strip().lower()
password = request.form.get("password", "").strip()
confirm = request.form.get("confirm", "").strip()
if not all([username,email,password, confirm]):
flash("Fill in every field!", "error")
return redirect(url_for("auth.register_form"))
email_regex = r"^[\w\.-]+@[\w\.-]+\.\w+$"
if not re.match(email_regex, email):
flash("invalid email address!", "error")
return redirect(url_for("auth.register"))
if password != confirm:
flash("invalid password", "error")
return redirect(url_for("auth.register_form"))
if not re.search(r"[A-Z]", password):
flash("invalid password", "error")
return redirect(url_for("auth.register"))
if not re.search(r"[a-z]", password):
flash("invalid password", "error")
return redirect(url_for("register"))
if not re.search(r"\d", password):
flash("invalid password", "error")
return redirect(url_for("auth.register"))
exiting_user = User.query.filter_by(email=email).first()
if exiting_user:
flash("User already exits!", "error")
return redirect(url_for("auth.register"))
user = User(username=username, email=email)
try:
user.set_password(password)
except ValueError as e:
flash(str(e), "error")
return redirect(url_for("auth.register_form"))
try:
db.session.add(user)
db.session.commit()
except IntegrityError:
db.session.rollback()
flash("Username or email already registerd", "error")
return redirect(url_for("auth.register_form"))
flash("Account has been created!", "success")
return redirect(url_for("auth.login_form"))
@bp.get("/login")
def login_form():
if current_user.is_authenticated:
return redirect(url_for("playlist.home"))
return render_template("auth/login.html")
@bp.post("/login")
def login():
email = request.form.get("email", "").strip().lower()
password = request.form.get("password", "")
user = User.query.filter_by(email=email).first()
if not user or not user.check_password(password):
flash("wrong email or password", "error")
return redirect(url_for("auth.login_form"))
login_user(user)
next_page = request.args.get("next")
return redirect(next_page or url_for("playlist.home"))
@bp.get("/logout")
def logout():
logout_user()
flash("You are now logged out!", "error")
return redirect(url_for("auth.login_form"))