Skip to content

Latest commit

 

History

17 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Service Marketplace (CSCI334) – Flask App

A full‑stack service marketplace web application built with Flask + SQLAlchemy + Bootstrap 5. It supports two roles:

  • Users – sign up, post requirements, manage bookings.
  • Providers – sign up, list services, browse user posts and bid.

The app includes authentication, a search page with filters and sorting, simple dashboards, and demo pages for bookings/services.


✨ Features

  • Auth & Roles: Separate signup/signin for Users and Providers, secure passwords (Werkzeug hashing).
  • Search & Sort: Filter services by name, location, category, price range; sort by newest/oldest and price.
  • Posts & Bids: Users create posts (requirements). Providers can place bids (model included).
  • Dashboards: Role‑aware dashboards routed by session role.
  • Demo Pages: /user/bookings, /provider/services sample listings.
  • Responsive UI: Bootstrap 5 + custom CSS (style.css, index.css, search.css).
  • SQLite: Auto‑created database under db/users.db.

🗂 Project Structure

project-root/
├── app.py
├── models.py
├── db/                       # created on first run
│   └── users.db
├── templates/
│   ├── index.html
│   ├── search.html
│   ├── user_bookings.html
│   └── provider_services.html
└── static/
    ├── css/
    │   ├── style.css
    │   ├── index.css
    │   └── search.css
    └── img/
        ├── name.png
        └── placeholder.png

Important: Flask loads templates from the templates/ folder and static files from static/. Make sure your files match the structure above (move them if needed).


🚀 Quick Start

1) Clone & create a virtual environment

# Python 3.10+ recommended
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS/Linux
source .venv/bin/activate

2) Install dependencies

pip install Flask==3.0.3 Flask-SQLAlchemy==3.1.1 Werkzeug==3.0.3

3) Project layout

Create folders if they don’t exist:

mkdir -p templates static/css static/img db

Place the HTML files into templates/, the CSS into static/css/, and images (e.g., name.png, placeholder.png) into static/img/.

4) Run the app

python app.py

Visit: http://127.0.0.1:5000/

On first run, the SQLite DB will be created at db/users.db.


⚙️ Configuration

app.py uses:

  • SECRET_KEY set inline. For production, supply an environment variable and change app config:

    app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-only-secret')
  • SQLite path is set to a safe subfolder:

    basedir = os.path.abspath(os.path.dirname(__file__))
    db_dir = os.path.join(basedir, 'db')
    os.makedirs(db_dir, exist_ok=True)
    db_path = os.path.join(db_dir, 'users.db')
    app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{db_path}"

🧱 Data Model (SQLAlchemy)

There are two copies of models across app.py and models.py. Prefer the single‑source approach (see Recommended Cleanup below).

From models.py:

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    full_name = db.Column(db.String(100))
    email = db.Column(db.String(120), unique=True)
    password = db.Column(db.String(200))
    phone = db.Column(db.String(20))
    address = db.Column(db.String(200))
    dob = db.Column(db.String(20))

class Provider(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    full_name = db.Column(db.String(100))
    email = db.Column(db.String(120), unique=True)
    password = db.Column(db.String(200))
    phone = db.Column(db.String(20))
    address = db.Column(db.String(200))
    dob = db.Column(db.String(20))
    about = db.Column(db.Text)
    category = db.Column(db.String(50))
    skills = db.Column(db.String(200))

class Post(db.Model):
    id          = db.Column(db.Integer, primary_key=True)
    user_id     = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    title       = db.Column(db.String(100), nullable=False)
    description = db.Column(db.Text,    nullable=False)
    budget      = db.Column(db.Float,   nullable=False)
    deadline    = db.Column(db.String(20), nullable=False)
    user        = db.relationship('User', backref='posts')

From app.py (extra models used by routes):

class Booking(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    service_name = db.Column(db.String(100))
    date = db.Column(db.String(20))
    status = db.Column(db.String(20))
    user = db.relationship('User', backref='bookings')

class Service(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    provider_id = db.Column(db.Integer, db.ForeignKey('provider.id'))
    title = db.Column(db.String(100))
    description = db.Column(db.Text)
    price = db.Column(db.Float)
    provider = db.relationship('Provider', backref='services')

class Bid(db.Model):
    id          = db.Column(db.Integer, primary_key=True)
    post_id     = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)
    provider_id = db.Column(db.Integer, db.ForeignKey('provider.id'), nullable=False)
    amount      = db.Column(db.Float,   nullable=False)
    message     = db.Column(db.Text)
    post        = db.relationship('Post',     backref='bids')
    provider    = db.relationship('Provider', backref='bids')

Tip: Consolidate all models into models.py and import them in app.py to avoid schema drift.


🌐 Key Routes

  • GET /index.html (home + hero + search CTA, modals for auth).
  • GET|POST /searchsearch.html
    • POST accepts name, location, category, priceRange, sortBy and returns filtered services.
    • GET shows latest services (default order: newest).
  • POST /signup/user – create a User (prevents email clash with Provider).
  • POST /signup/provider – create a Provider (prevents email clash with User).
  • POST /signin – sign in as User or Provider (shared form; role stored in session).
  • GET /logout – clear session.
  • GET /user/dashboard – shows user profile + their posts.
  • GET /provider/dashboard – shows provider profile + all posts (to bid later).
  • POST /update_profile – update basic profile fields (name/address/dob) by role.
  • POST /create_post – User creates a requirement post.
  • GET /view_bids/<post_id> – view bids for a given post (template bids.html expected).
  • POST /delete_post/<post_id> – delete user’s own post.
  • GET /user/bookings – demo bookings page.
  • GET /provider/services – demo provider services page.

🔍 Search Page (UX notes)

templates/search.html exposes dropdowns for Location, Category, Price Range, and Sort By. Hidden inputs carry the chosen values to the server. The results section renders cards from Service + Provider info and supports a placeholder image when a provider has no image_url.


🧪 Seeding Sample Data (optional)

Create some quick demo rows via Flask shell:

python
>>> from app import db, User, Provider, Service
>>> db.create_all()
>>> p = Provider(full_name="Jane Waters", email="jane@p.com", password="hashed", phone="+61 412 345 678",
...              address="25 King St, Sydney", dob="1990-04-12", about="Experienced cleaner.",
...              category="cleaning", skills="deep cleaning,vacuuming,sanitizing")
>>> db.session.add(p); db.session.commit()
>>> s = Service(provider_id=p.id, title="Premium Home Cleaning", description="Deep cleaning and sanitation.", price=149.0)
>>> db.session.add(s); db.session.commit()

Replace "hashed" with an actual hashed password:

from werkzeug.security import generate_password_hash
p.password = generate_password_hash("123")
db.session.commit()

🧹 Recommended Cleanup (next steps)

  1. Single models source: Move all models into models.py and import in app.py:

    from models import db, User, Provider, Service, Booking, Post, Bid
    db.init_app(app)

    And call with app.app_context(): db.create_all() at startup.

  2. Templates: Ensure all templates live in templates/. Missing pages referenced by routes (e.g., bids.html, dashboard.html, about_us.html, contact.html, blog.html) should be added to avoid TemplateNotFound.

  3. Static paths: In templates, reference CSS/JS via url_for('static', filename='css/style.css') to avoid broken relative paths.

  4. Forms JS: Ensure static/fuc/account.js and static/fuc/search.js exist or remove those <script> tags.

  5. Provider images: If you use service.provider.image_url, add that column to Provider or guard with default placeholders.

  6. CSRF & Security: Consider Flask‑WTF for CSRF protection; move secrets to environment variables.

  7. Migrations: Add Alembic/Flask‑Migrate for schema changes.


🧰 Troubleshooting

  • TemplateNotFound: Check file names/locations and that you’re calling render_template('name.html') for a file under templates/.
  • Static files not loading: Use {{ url_for('static', filename='css/style.css') }} and ensure static/ exists.
  • BadRequestKeyError: Access missing form keys safely, e.g. request.form.get('field').
  • Duplicate emails: Both signup routes prevent email reuse across roles.
  • Flask session issues: After logout, both email and role keys are removed (session.modified = True).

🗺 Roadmap

  • Provider profiles (image, portfolio, availability).
  • Real messaging between Users and Providers.
  • Invoices & payments (intent → hold → capture).
  • Reviews & ratings after completion.
  • Notifications (success toasts + email).
  • Pagination for search results.
  • RBAC hardening and CSRF everywhere.

📄 License

Add your preferred license (MIT, Apache-2.0, etc.).


🙌 Credits

Built with ❤️ using Flask, SQLAlchemy, and Bootstrap 5.

About

ServiceLink is a service marketplace connecting skilled professionals with customers needing services like plumbing, electrical work, repairs, and freelancing. Providers list expertise, and users book easily with secure payments, real-time tracking, and reviews for a seamless experience.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages