-
Notifications
You must be signed in to change notification settings - Fork 0
Added Login and SignUp pages #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,3 +33,7 @@ __pycache__/ | |
|
|
||
| # OS | ||
| .DS_Store | ||
|
|
||
| # Flask instance folder (local DB, secrets) | ||
| backend/instance/ | ||
| instance/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,8 @@ | ||
| # Flashify | ||
| e-commerce platform for instant deliveries within 20 mins. | ||
|
|
||
|
|
||
| To download dependencies and create db locally: | ||
|
|
||
| pip install -r requirements.txt | ||
| flask db init / create_all |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| Flask==3.1.2 | ||
| Flask-Cors==6.0.2 | ||
| Flask-JWT-Extended==4.7.1 | ||
| Flask-SQLAlchemy==3.1.1 | ||
| python-dotenv==1.2.1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,23 +1,43 @@ | ||
| from flask import Flask | ||
| from flask_cors import CORS | ||
| from flask_sqlalchemy import SQLAlchemy | ||
| from flask_jwt_extended import JWTManager | ||
|
|
||
| db = SQLAlchemy() | ||
| jwt = JWTManager() | ||
|
|
||
| def create_app(): | ||
| app = Flask(__name__) | ||
| app.config.from_object("config.Config") | ||
|
|
||
| CORS(app) # allow frontend calls | ||
| db.init_app(app) # database hookup | ||
| # Initialize extensions FIRST | ||
| db.init_app(app) | ||
| jwt.init_app(app) | ||
|
|
||
| # Apply CORS BEFORE registering blueprints | ||
| CORS(app) | ||
|
|
||
| # Ensure models are imported before create_all | ||
| from . import models # noqa: F401 | ||
|
|
||
| @app.route("/health") | ||
| def health(): | ||
| return {"status": "ok"} | ||
| # Create DB tables | ||
| with app.app_context(): | ||
| db.create_all() | ||
|
|
||
| # Register API routes (Blueprint) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blueprint? |
||
| from .routes.auth_routes import api | ||
| from .routes.location_routes import location_bp | ||
| from .routes.address_routes import address_bp | ||
|
|
||
| from .routes import api | ||
| app.register_blueprint(api) | ||
| app.register_blueprint(location_bp) | ||
| app.register_blueprint(address_bp) | ||
|
|
||
| return app | ||
| #Debug: Print registered routes | ||
| print("\n=== REGISTERED ROUTES ===") | ||
| for r in app.url_map.iter_rules(): | ||
| print(r) | ||
| print("========================\n") | ||
|
|
||
|
|
||
| return app | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,59 @@ | ||
| from datetime import datetime | ||
| from werkzeug.security import generate_password_hash, check_password_hash | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use passlib or bcrypt. https://medium.com/top-python-libraries/hashing-passwords-using-the-top-5-python-libraries-5ec530973b17 |
||
| from . import db | ||
|
|
||
|
|
||
| class User(db.Model): | ||
| __tablename__ = "users" | ||
|
|
||
| id = db.Column(db.Integer, primary_key=True) | ||
|
|
||
| email = db.Column(db.String(255), unique=True, nullable=False, index=True) | ||
| password_hash = db.Column(db.String(255), nullable=False) | ||
|
|
||
| # Location selection | ||
| location_label = db.Column(db.String(255), nullable=True) | ||
| location_lat = db.Column(db.Float, nullable=True) | ||
| location_lng = db.Column(db.Float, nullable=True) | ||
|
|
||
| # relationship (optional but recommended) | ||
| addresses = db.relationship( | ||
| "Address", | ||
| backref="user", | ||
| lazy=True, | ||
| cascade="all, delete-orphan", | ||
| ) | ||
|
|
||
| def set_password(self, password: str) -> None: | ||
| self.password_hash = generate_password_hash(password) | ||
|
|
||
| def check_password(self, password: str) -> bool: | ||
| return check_password_hash(self.password_hash, password) | ||
|
|
||
|
|
||
| # NEW TABLE | ||
| class Address(db.Model): | ||
| __tablename__ = "addresses" | ||
|
|
||
| id = db.Column(db.Integer, primary_key=True) | ||
| email = db.Column(db.String(120), unique=True, nullable=False) | ||
|
|
||
| user_id = db.Column( | ||
| db.Integer, | ||
| db.ForeignKey("users.id"), | ||
| nullable=False, | ||
| index=True, | ||
| ) | ||
|
|
||
| label = db.Column(db.String(50), nullable=False) # Home / Work / Other | ||
| line1 = db.Column(db.String(255), nullable=False) | ||
| line2 = db.Column(db.String(255), nullable=True) | ||
| city = db.Column(db.String(100), nullable=False) | ||
| state = db.Column(db.String(100), nullable=False) | ||
| zip = db.Column(db.String(20), nullable=False) | ||
|
|
||
| lat = db.Column(db.Float, nullable=True) | ||
| lng = db.Column(db.Float, nullable=True) | ||
|
|
||
| is_default = db.Column(db.Boolean, default=False, nullable=False) | ||
|
|
||
| created_at = db.Column(db.DateTime, default=datetime.utcnow) | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| from flask import Blueprint, request, jsonify | ||
| from flask_jwt_extended import jwt_required, get_jwt_identity | ||
|
|
||
| from .. import db | ||
| from ..models import Address | ||
|
|
||
| address_bp = Blueprint("addresses", __name__, url_prefix="/api/addresses") | ||
|
|
||
| @address_bp.before_request | ||
| def handle_preflight(): | ||
| if request.method == "OPTIONS": | ||
| return "", 204 | ||
|
|
||
| @address_bp.route("", methods=["GET"]) | ||
| @jwt_required() | ||
| def list_addresses(): | ||
| user_id = get_jwt_identity() | ||
| addresses = ( | ||
| Address.query.filter_by(user_id=user_id) | ||
| .order_by(Address.id.desc()) | ||
| .all() | ||
| ) | ||
| return jsonify([serialize_address(a) for a in addresses]), 200 | ||
|
|
||
|
|
||
| @address_bp.route("", methods=["POST"]) | ||
| @jwt_required() | ||
| def create_address(): | ||
| user_id = get_jwt_identity() | ||
| data = request.get_json() or {} | ||
|
|
||
| required = ["label", "line1", "city", "state", "zip"] | ||
| missing = [k for k in required if not data.get(k)] | ||
| if missing: | ||
| return jsonify({"message": f"Missing fields: {', '.join(missing)}"}), 400 | ||
|
|
||
| is_default = bool(data.get("is_default", False)) | ||
| if is_default: | ||
| Address.query.filter_by(user_id=user_id, is_default=True).update({"is_default": False}) | ||
|
|
||
| addr = Address( | ||
| user_id=user_id, | ||
| label=data["label"], | ||
| line1=data["line1"], | ||
| line2=data.get("line2"), | ||
| city=data["city"], | ||
| state=data["state"], | ||
| zip=data["zip"], | ||
| lat=data.get("lat"), | ||
| lng=data.get("lng"), | ||
| is_default=is_default, | ||
| ) | ||
| db.session.add(addr) | ||
| db.session.commit() | ||
|
|
||
| return jsonify(serialize_address(addr)), 201 | ||
|
|
||
|
|
||
| @address_bp.route("/<int:address_id>/default", methods=["PATCH"]) | ||
| @jwt_required() | ||
| def set_default_address(address_id): | ||
| user_id = get_jwt_identity() | ||
| addr = Address.query.filter_by(id=address_id, user_id=user_id).first() | ||
| if not addr: | ||
| return jsonify({"message": "Address not found"}), 404 | ||
|
|
||
| Address.query.filter_by(user_id=user_id, is_default=True).update({"is_default": False}) | ||
| addr.is_default = True | ||
| db.session.commit() | ||
|
|
||
| return jsonify({"message": "Default address updated"}), 200 | ||
|
|
||
|
|
||
| @address_bp.route("/<int:address_id>", methods=["DELETE"]) | ||
| @jwt_required() | ||
| def delete_address(address_id): | ||
| user_id = get_jwt_identity() | ||
| addr = Address.query.filter_by(id=address_id, user_id=user_id).first() | ||
| if not addr: | ||
| return jsonify({"message": "Address not found"}), 404 | ||
|
|
||
| db.session.delete(addr) | ||
| db.session.commit() | ||
| return jsonify({"message": "Deleted"}), 200 | ||
|
|
||
|
|
||
| def serialize_address(a: Address): | ||
| return { | ||
| "id": a.id, | ||
| "label": a.label, | ||
| "line1": a.line1, | ||
| "line2": a.line2, | ||
| "city": a.city, | ||
| "state": a.state, | ||
| "zip": a.zip, | ||
| "lat": a.lat, | ||
| "lng": a.lng, | ||
| "is_default": a.is_default, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| from flask import Blueprint, request, jsonify | ||
| from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity | ||
| from ..models import User | ||
| from .. import db | ||
|
|
||
| api = Blueprint("api", __name__) | ||
|
|
||
| @api.post("/api/auth/signup") | ||
| def signup(): | ||
| data = request.get_json(silent=True) or {} | ||
| email = (data.get("email") or "").strip().lower() | ||
| password = data.get("password") or "" | ||
|
|
||
| if not email or not password: | ||
| return jsonify(message="Email and password are required."), 400 | ||
|
|
||
| existing = User.query.filter_by(email=email).first() | ||
| if existing: | ||
| return jsonify(message="User already exists."), 409 | ||
|
|
||
| user = User(email=email) | ||
| user.set_password(password) | ||
|
|
||
| db.session.add(user) | ||
| db.session.commit() | ||
|
|
||
| return jsonify(message="Signup successful."), 201 | ||
|
|
||
|
|
||
| @api.post("/api/auth/login") | ||
| def login(): | ||
| print() | ||
| data = request.get_json(silent=True) or {} | ||
| email = (data.get("email") or "").strip().lower() | ||
| password = data.get("password") or "" | ||
|
|
||
| print(f"[LOGIN] Request data: email={email}, password={'*' * len(password)}") # ✅ ADD | ||
|
|
||
| if not email or not password: | ||
| return jsonify(message="Email and password are required."), 400 | ||
|
|
||
| user = User.query.filter_by(email=email).first() | ||
| if not user or not user.check_password(password): | ||
| return jsonify(message="Invalid email or password."), 401 | ||
|
|
||
| # JWT identity can be email for now | ||
| token = create_access_token(identity=email) | ||
|
|
||
| return jsonify(token=token), 200 | ||
|
|
||
|
|
||
| @api.get("/api/me") | ||
| @jwt_required() | ||
| def me(): | ||
| email = get_jwt_identity() | ||
| return jsonify(email=email), 200 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| from flask import Blueprint, request, jsonify | ||
| from flask_jwt_extended import jwt_required, get_jwt_identity | ||
|
|
||
| from .. import db | ||
| from ..models import User | ||
|
|
||
| location_bp = Blueprint("location", __name__, url_prefix="/api/location") | ||
|
|
||
|
|
||
| @location_bp.route("/select", methods=["POST", "OPTIONS"]) | ||
|
|
||
| def select_location(): | ||
| user_id = get_jwt_identity() | ||
| data = request.get_json() or {} | ||
|
|
||
| label = data.get("label") | ||
| lat = data.get("lat") | ||
| lng = data.get("lng") | ||
|
|
||
| if not label: | ||
| return jsonify({"message": "label is required"}), 400 | ||
|
|
||
| user = User.query.get(user_id) | ||
| if not user: | ||
| return jsonify({"message": "User not found"}), 404 | ||
|
|
||
| user.location_label = label | ||
| user.location_lat = lat | ||
| user.location_lng = lng | ||
| db.session.commit() | ||
|
|
||
| return jsonify( | ||
| { | ||
| "message": "Location saved", | ||
| "location": {"label": user.location_label, "lat": user.location_lat, "lng": user.location_lng}, | ||
| } | ||
| ), 200 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's oqa: F401?