Skip to content
Merged
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
12 changes: 11 additions & 1 deletion backend/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,17 @@ def create_app():
app.config["SESSION_TYPE"] = "filesystem"

Session(app)
CORS(app, origins=os.environ.get("CORS_ORIGINS", "http://localhost:5173"), supports_credentials=True)
cors_origins = os.environ.get("CORS_ORIGINS", "http://localhost:5173")
CORS(app, origins=cors_origins, supports_credentials=True)

@app.after_request
def add_cors_headers(response):
response.headers["Access-Control-Allow-Origin"] = cors_origins
response.headers["Access-Control-Allow-Credentials"] = "true"
response.headers["Access-Control-Allow-Headers"] = "Content-Type"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
return response

init_oauth(app)

app.register_blueprint(auth_bp)
Expand Down
2 changes: 2 additions & 0 deletions backend/models/inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ class Stock(db.Model):
batch_number = db.Column(db.String(100))
expiration_date = db.Column(db.Date)
origin_date = db.Column(db.DateTime, default=datetime.utcnow)

item = db.relationship("Item", backref="stocks")
44 changes: 18 additions & 26 deletions backend/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from typing import ClassVar

from database import db
from models.catalog import Item


class User(db.Model):
Expand Down Expand Up @@ -49,7 +48,7 @@ def to_dict(self):


class Producer(User):
__tablename__ = "producers"
__tablename__: ClassVar[str] = "producers"
id = db.Column(db.Integer, db.ForeignKey("users.id", ondelete="CASCADE"), primary_key=True)
company_name = db.Column(db.String(255), nullable=False)
primary_address = db.Column(db.Text, nullable=False)
Expand All @@ -58,28 +57,20 @@ class Producer(User):
lat = db.Column(db.Float, nullable=True)
lng = db.Column(db.Float, nullable=True)

__mapper_args__ = {"polymorphic_identity": "producer"}
inventory = db.relationship("Inventory", backref="producer_user", uselist=False, foreign_keys="Inventory.producer_id")

def to_dict(self):
data = super().to_dict()
data["company_name"] = self.company_name
data["primary_address"] = self.primary_address
data["lat"] = self.lat
data["lng"] = self.lng

items = Item.query.filter_by(producer_id=self.id).all()

inventory_data = []
for item in items:
base_price = min([float(p.price_per_unit) for p in item.prices]) if item.prices else 0

inventory_data.append(
{"id": item.id, "name": item.name, "description": item.description, "base_price": base_price}
)
__mapper_args__: ClassVar[dict] = {"polymorphic_identity": "producer"}

data["inventory"] = inventory_data

return data
def to_dict(self):
return {
**super().to_dict(),
"company_name": self.company_name,
"primary_address": self.primary_address,
"company_description": self.company_description,
"lat": self.lat,
"lng": self.lng,
"inventory_id": self.inventory.id if self.inventory else None,
}


class Retailer(User):
Expand All @@ -92,10 +83,11 @@ class Retailer(User):
__mapper_args__: ClassVar[dict] = {"polymorphic_identity": "retailer"}

def to_dict(self):
data = super().to_dict()
data["company_name"] = self.company_name
data["store_address"] = self.store_address
return data
return {
**super().to_dict(),
"company_name": self.company_name,
"store_address": self.store_address,
}


class Consumer(User):
Expand Down
60 changes: 52 additions & 8 deletions backend/routes/auth.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import logging
import os

from flask import Blueprint, jsonify, redirect, session, url_for
from flask import Blueprint, jsonify, redirect, request, session, url_for
from sqlalchemy import text

from config.oauth import oauth
from models.user import User
from database import db
from models.inventory import Inventory
from models.user import Producer, Retailer, User

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -36,12 +39,7 @@ def callback():
picture=user_info.get("picture"),
)

session["user"] = {
"id": user.id,
"email": user.email,
"picture": user.picture,
"user_type": user.user_type,
}
session["user"] = {**user.to_dict(), "picture": user.picture}
session["authenticated"] = True

frontend_url = os.environ.get("CORS_ORIGINS", "http://localhost:5173")
Expand All @@ -60,6 +58,52 @@ def logout():
return jsonify({"success": True})


@auth_bp.route("/onboard", methods=["POST"])
def onboard():
if not session.get("authenticated"):
return jsonify({"error": "Not authenticated"}), 401

data = request.get_json()
user_type = data.get("user_type", "").lower()
company_name = data.get("company_name", "").strip()
address = data.get("address", "").strip()
description = data.get("description", "").strip()

if user_type not in ("producer", "retailer"):
return jsonify({"error": "user_type must be producer or retailer"}), 400
if not company_name:
return jsonify({"error": "company_name is required"}), 400
if not address:
return jsonify({"error": "address is required"}), 400

user_id = session["user"]["id"]

# Remove the existing consumer row and update the base user type
db.session.execute(text("DELETE FROM consumers WHERE id = :id"), {"id": user_id})
db.session.execute(text("UPDATE users SET user_type = :ut WHERE id = :id"), {"ut": user_type, "id": user_id})

if user_type == "producer":
db.session.execute(
text("INSERT INTO producers (id, company_name, primary_address, description) VALUES (:id, :cn, :addr, :desc)"),
{"id": user_id, "cn": company_name, "addr": address, "desc": description or None},
)
db.session.flush()
db.session.add(Inventory(producer_id=user_id))
else:
db.session.execute(
text("INSERT INTO retailers (id, company_name, store_address) VALUES (:id, :cn, :addr)"),
{"id": user_id, "cn": company_name, "addr": address},
)

db.session.commit()

user = db.session.get(User, user_id)
db.session.refresh(user)
session["user"] = {**user.to_dict(), "picture": user.picture}

return jsonify({"user": session["user"]}), 200


@auth_bp.route("/user")
def get_user():
if session.get("authenticated") and "user" in session:
Expand Down
10 changes: 8 additions & 2 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { useState } from 'react';

Check warning on line 2 in frontend/src/App.jsx

View workflow job for this annotation

GitHub Actions / frontend-build

'useState' is defined but never used. Allowed unused vars must match /^[A-Z_]/u
import "./App.css";
import { AuthProvider } from "./contexts/AuthProvider.jsx";
import PrivateRoute from "./components/PrivateRoute.jsx";
Expand All @@ -8,9 +8,10 @@
import SignUp from "./pages/SignUp.jsx";
import SignIn from "./pages/SignIn.jsx";
import MapView from "./pages/MapView.jsx";
import Onboard from "./pages/Onboard.jsx"
import Onboard from "./pages/Onboard.jsx";
import Clippy from "./components/Clippy/Clippy.jsx";
import AuthHandler from "./components/AuthHandler.jsx";
import ProducerDashboard from "./components/ProducerDashboard/ProducerDashboard.jsx";

function App() {

Expand All @@ -22,7 +23,7 @@
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={
<PrivateRoute>
<PrivateRoute allowedRoles={['producer', 'retailer']} redirectTo="/onboard">
<Dashboard />
</PrivateRoute>
} />
Expand All @@ -33,6 +34,11 @@
<Onboard />
</PrivateRoute>
} />
<Route path="/inventory" element={
<PrivateRoute allowedRoles={['producer']}>
<ProducerDashboard />
</PrivateRoute>
} />
<Route path="/map" element={<MapView />} />
</Routes>
</AuthProvider>
Expand Down
Loading
Loading