Skip to content

Commit

Permalink
Merge pull request #1 from Esekyi/feature/recipe_forms
Browse files Browse the repository at this point in the history
Feature/recipe forms
  • Loading branch information
Esekyi authored Aug 27, 2024
2 parents 439a1bb + 948fbb0 commit e183fca
Show file tree
Hide file tree
Showing 24 changed files with 2,226 additions and 49 deletions.
26 changes: 26 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
.venv/
__pycache__/
.env
notice.txt

# IDE and editor files
.vscode/
.idea/
*.swp
*.swo
.DS_Store

# Compiled Python files
*.pyc
*.pyo
*.pyd
# Temporary files
*.bak
*.tmp

# Node.js
node_modules/
npm-debug.log
yarn-error.log
yarn-debug.log
.pnpm-debug.log

# Operating system files
Thumbs.db
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,65 @@
# SpiceShare - A recipe-sharing web app

SpiceShare is a web application that allows users to share and discover recipes from around the world.

## Features

- User authentication
- Recipe creation and sharing
- Search and filter recipes
- Favorite and save recipes (later updates)
- User profiles

## Prerequisites
Before you begin, ensure you have met the following requirements:

- Node.js (v14.x or later)
- npm (v6.x or later)

--- this is only used to build tailwind css

## Installation

To install SpiceShare, follow these steps:

1. Clone the repository:
```
https://github.com/Esekyi/SpiceShare.git
cd spiceshare
```

2. Install dependencies:
python and flask
```
python3 -m venv .venv
python3 -m pip install -r requirements.txt
```
Node for tailwind css build
```
npm install
# or
yarn install
```




## Contributing

Contributions to SpiceShare are welcome! Please follow these steps:

1. Fork the repository
2. Create a new branch: `git checkout -b feature/your-feature-name`
3. Make your changes and commit them: `git commit -m 'Add some feature'`
4. Push to the branch: `git push origin feature/your-feature-name`
5. Submit a pull request

Please make sure to update tests as appropriate and adhere to the project's coding standards.

## License

[MIT License](LICENSE)

## Contact

If you have any questions or feedback, please open an issue on GitHub or contact the maintainer at [email protected].
10 changes: 8 additions & 2 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
from flask_migrate import Migrate
from app.config import Config
from flask_login import LoginManager
from app.models.user import User
from uuid import UUID
import os
from build import build_css

login_manager = LoginManager()
login_manager.login_view = 'auth.login'
Expand All @@ -14,6 +15,9 @@
migrate = Migrate()

def create_app():
if not os.environ.get('FLASK_DEBUG'):
build_css()

app = Flask(__name__)
app.config.from_object(Config)

Expand All @@ -23,15 +27,17 @@ def create_app():
login_manager.init_app(app)

# register blueprint
from app.routes import user_routes, recipe_routes
from app.routes import user_routes, recipe_routes, auth_routes
app.register_blueprint(user_routes.bp)
app.register_blueprint(recipe_routes.bp)
app.register_blueprint(auth_routes.auth_bp)


return app

@login_manager.user_loader
def load_user(user_id):
from app.models.user import User
try:
user_uuid = UUID(user_id)
return User.query.get(user_uuid)
Expand Down
4 changes: 2 additions & 2 deletions app/models/category.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from app import db
from datetime import datetime
import uuid
from sqlalchemy.dialects.postgresql import UUID


class Category(db.Model):
id = db.Column(db.String(36), primary_key=True,
default=lambda: str(uuid.uuid4()))
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = db.Column(db.String(80), unique=True, nullable=False)
description = db.Column(db.Text)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
Expand Down
11 changes: 6 additions & 5 deletions app/models/comment.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
from app import db
from datetime import datetime
import uuid
from sqlalchemy.dialects.postgresql import UUID


class Comment(db.Model):
id = db.Column(db.String(36), primary_key=True,
default=lambda: str(uuid.uuid4()))
id = db.Column(UUID(as_uuid=True), primary_key=True,
default=uuid.uuid4)
text = db.Column(db.Text, nullable=False)
user_id = db.Column(db.String(36), db.ForeignKey(
'user.id'), nullable=False)
recipe_id = db.Column(db.String(36), db.ForeignKey(
user_id = db.Column(UUID(as_uuid=True),
db.ForeignKey('user.id'), nullable=False)
recipe_id = db.Column(UUID(as_uuid=True), db.ForeignKey(
'recipe.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(
Expand Down
7 changes: 4 additions & 3 deletions app/models/ingredient.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from app import db
from datetime import datetime
import uuid
from sqlalchemy.dialects.postgresql import UUID


class Ingredient(db.Model):
id = db.Column(db.String(36), primary_key=True,
default=lambda: str(uuid.uuid4()))
id = db.Column(UUID(as_uuid=True), primary_key=True,
default=uuid.uuid4)
name = db.Column(db.String(120), nullable=False)
quantity = db.Column(db.String(50), nullable=False)
recipe_id = db.Column(db.String(36), db.ForeignKey(
recipe_id = db.Column(UUID(as_uuid=True), db.ForeignKey(
'recipe.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(
Expand Down
9 changes: 5 additions & 4 deletions app/models/recipe.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
from app import db
from datetime import datetime
import uuid
from sqlalchemy.dialects.postgresql import UUID


class Recipe(db.Model):
id = db.Column(db.String(36), primary_key=True,
default=lambda: str(uuid.uuid4()))
id = db.Column(UUID(as_uuid=True), primary_key=True,
default=uuid.uuid4)
title = db.Column(db.String(120), nullable=False)
description = db.Column(db.Text, nullable=False)
instructions = db.Column(db.Text, nullable=False)
prep_time = db.Column(db.Integer) # in minutes
cook_time = db.Column(db.Integer) # in minutes
servings = db.Column(db.Integer)
image_url = db.Column(db.String(255))
category_id = db.Column(db.String(36), db.ForeignKey(
category_id = db.Column(UUID(as_uuid=True), db.ForeignKey(
'category.id'), nullable=False)
user_id = db.Column(db.String(36), db.ForeignKey(
user_id = db.Column(UUID(as_uuid=True), db.ForeignKey(
'user.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(
Expand Down
9 changes: 6 additions & 3 deletions app/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
from datetime import datetime
import uuid
from flask_login import UserMixin
from sqlalchemy.dialects.postgresql import UUID


class User(db.Model, UserMixin):
id = db.Column(db.String(36), primary_key=True,
default=lambda: str(uuid.uuid4()))
id = db.Column(UUID(as_uuid=True), primary_key=True,
default = uuid.uuid4)
first_name = db.Column(db.String(50), nullable=False)
last_name = db.Column(db.String(50), nullable=False)
username = db.Column(db.String(80), unique=True, nullable=False, index=True)
email = db.Column(db.String(120), unique=True, nullable=False, index=True)
password_hash = db.Column(db.String(128), nullable=False)
password_hash = db.Column(db.String(255), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(
db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
Expand Down
33 changes: 33 additions & 0 deletions app/routes/auth_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from flask import Blueprint, render_template, request, flash, redirect, url_for, jsonify
from flask_login import login_user, logout_user, login_required, current_user
from werkzeug.security import check_password_hash
from app.models.user import User
from app import db

"""Route for basic authentication"""

auth_bp = Blueprint('auth', __name__)

@auth_bp.route('/login', methods=['GET','POST'])
def login():
"""Login authentication handler"""
if request.method == 'POST':
email = request.form["email"]
password = request.form["password"]
user = User.query.filter_by(email=email).first()

if user and check_password_hash(user.password_hash, password):
login_user(user)
return jsonify({"success": "you're logged in"})

flash('Invalid Credentials', 'warning')

return render_template('login.html')

@auth_bp.route('/logout')
@login_required
def logout():
"""logout handler"""
logout_user()
return redirect(url_for('auth.login'))

111 changes: 99 additions & 12 deletions app/routes/user_routes.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,107 @@
from flask import Blueprint, jsonify, request
"""User services routes"""
from flask import Blueprint, jsonify, request, flash, redirect, url_for, render_template
from app import db
from app.services.user_services import get_all_users
from app.services.user_services import create_user
from app.services.user_services import get_all_users, delete_user, get_user_by_id, update_user, create_user, get_user_by_email
from flask_login import login_required, current_user

# user routes blueprint
bp = Blueprint('user_routes', __name__)

@bp.route('/users', methods=['GET'])
def list_users():
users = get_all_users()
return jsonify([user.username for user in users])
"""get all users in db"""
users = get_all_users()
for user in users:
return jsonify({
"id": user.id,
"name": user.first_name + ' ' + user.last_name,
"email": user.email,
"password": user.password_hash
})


@bp.route('/users', methods=['POST'])
def add_user():
data = request.get_json()
user = create_user(data['username'], data['email'])
db.session.add(user)
db.session.commit()
return jsonify({"message": "User created successfully"}), 201
@bp.route('/user/<uuid:user_id>', methods=["GET"])
@login_required
def get_user(user_id):
"""get a specific user by id"""
user = get_user_by_id(str(user_id))
if not user:
flash('User not found', 'danger')
return redirect(url_for('main.index'))
return jsonify({"username": user.username, "email": user.email})


@bp.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
data = request.form
first_name = data.get('first_name')
last_name = data.get('last_name')
username = data.get('username')
email = data.get('email')
password = data.get('password')

if not first_name or not last_name or not username or not email or not password:
flash('All fields are required', 'error')
return redirect(url_for('user_routes.register'))

existing_user = get_user_by_email(email)
if existing_user:
flash('Email already registered', 'error')
return redirect(url_for('user_routes.register'))

try:
create_user(first_name, last_name, password, email, username)
flash('Registration successful, proceed to login!', 'success')
return redirect(url_for('auth.login'))
except Exception as e:
db.session.rollback()
flash('An error occured during registeration. Please try again', 'error')
return redirect(url_for('user_routes.register'))

return render_template('register.html')


@bp.route('/user/<uuid:user_id>/edit', methods=["POST"])
@login_required
def edit_user(user_id):
"""Update user details by id"""
user = get_user_by_id(str(user_id))
if user and user.id == current_user.id:
data = request.form
new_email = data.get('email')

# run a quick db check to see if email already exist
all_users = get_all_users()
if any(u.email == new_email and u.id != user.id for u in all_users):
flash('Email already in use', 'danger')
return redirect(url_for('user_routes.get_user', user_id=user_id))

update_user(
user,
email=new_email,
password=data.get('password'),
first_name=data.get('first_name'),
last_name=data.get('last_name')
)

flash('Profile updated successfully', 'success')
return redirect(url_for('user_routes.get_user', user_id=user_id))

else:
flash('You are not authorized to edit this profile.', 'danger')
return redirect(url_for('main.index'))


@bp.route('/user/<uuid:user_id>/delete', methods=['POST'])
@login_required
def delete_user_profile(user_id):

user = get_user_by_id(user_id)
if user and user.id == current_user.id:
delete_user(user)
flash('Profile deleted succesfully.', 'success')
return redirect(url_for('main.index'))
else:
flash('You are not authorized to delete this profile.', 'danger')
return redirect(url_for('main.index'))
Loading

0 comments on commit e183fca

Please sign in to comment.