Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ __pycache__/

# OS
.DS_Store

# Flask instance folder (local DB, secrets)
backend/instance/
instance/
6 changes: 6 additions & 0 deletions README.md
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
5 changes: 5 additions & 0 deletions backend/Requirements.txt
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
34 changes: 27 additions & 7 deletions backend/app/__init__.py
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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's oqa: F401?


@app.route("/health")
def health():
return {"status": "ok"}
# Create DB tables
with app.app_context():
db.create_all()

# Register API routes (Blueprint)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The 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
56 changes: 55 additions & 1 deletion backend/app/models.py
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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
7 changes: 0 additions & 7 deletions backend/app/routes.py

This file was deleted.

99 changes: 99 additions & 0 deletions backend/app/routes/address_routes.py
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,
}
56 changes: 56 additions & 0 deletions backend/app/routes/auth_routes.py
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
37 changes: 37 additions & 0 deletions backend/app/routes/location_routes.py
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
9 changes: 8 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,15 @@
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "7.3.7",
"@mui/material": "7.3.7",
"@toolpad/core": "^0.16.0",
"axios": "^1.13.4",
"react": "^19.2.0",
"react-dom": "^19.2.0"
"react-dom": "^19.2.0",
"react-router-dom": "^7.13.0"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
Expand Down
Loading