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
10 changes: 5 additions & 5 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@


def register_blueprints():
from app.migrated_routes.category import bp as category_bp
from app.migrated_routes.subcategory import bp as subcategory_bp
from app.migrated_routes.product import bp as product_bp
from app.routes.category import bp as category_bp
from app.routes.subcategory import bp as subcategory_bp
from app.routes.product import bp as product_bp
from app.routes.auth import bp as auth_bp

api.register_blueprint(category_bp, url_prefix="/categories")
api.register_blueprint(subcategory_bp, url_prefix="/subcategories")
api.register_blueprint(product_bp, url_prefix="/products")
api.register_blueprint(auth_bp, url_prefix="/auth")


app = Flask(__name__)
Expand Down Expand Up @@ -73,8 +75,6 @@ def missing_token_callback(error):
return jsonify(code="authorization_required", error="JWT needed for this operation. Login, if needed."), 401


from app import routes

swagger_config = {
'openapi': '3.0.0',
'title': 'Ecommerce REST API',
Expand Down
7 changes: 2 additions & 5 deletions app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,8 @@ def get(email):
return User.query.filter_by(email_normalized=email_normalized).scalar()

def set_email(self, email):
try:
self.email_normalized = self._normalize_email(email)
self.email = email
except EmailNotValidError as e:
raise e
self.email_normalized = self._normalize_email(email)
self.email = email

def set_password(self, password):
# scrypt stores salt with the hash, which it uses to verify the password
Expand Down
135 changes: 0 additions & 135 deletions app/routes.py

This file was deleted.

File renamed without changes.
154 changes: 154 additions & 0 deletions app/routes/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
from email_validator import EmailNotValidError
from flask import jsonify, make_response
from flask.views import MethodView
from flask_jwt_extended import (
create_access_token,
create_refresh_token,
get_jwt_identity,
jwt_required,
)
from flask_smorest import Blueprint, abort
from sqlalchemy.exc import IntegrityError

from app import db
from app.models import User
from app.schemas import AuthIn, AuthOut

bp = Blueprint("auth", __name__)


@bp.route("/register")
class Register(MethodView):
@bp.arguments(AuthIn)
@bp.response(201)
def post(self, data):
"""
Register a new user.
---
tags:
- User
description: Register a new user.
requestBody:
required: true
description: email - Email id <br> password - Password
content:
application/json:
schema:
type: object
required:
- email
- password
properties:
email:
type: string
password:
type: string
responses:
201:
description: User registered successfully.
400:
description: Invalid input.
409:
description: Email already exists.
500:
description: Internal Server Error.
"""

user = User()
user.set_password(data["password"])

try:
user.set_email(data["email"])
db.session.add(user)
db.session.commit()
except IntegrityError:
db.session.rollback()
abort(make_response(jsonify(error="Email already exists"), 409))
except EmailNotValidError as e:
abort(
make_response(jsonify(code="invalid_email_format", error=str(e)), 422)
)

return {"message": "Registered!"}


@bp.route("/login")
class Login(MethodView):
"""Login a user and return access & refresh tokens."""

@bp.arguments(AuthIn)
@bp.response(200, AuthOut)
def post(self, data):
"""
Login a user.
---
tags:
- User
description: Login a user.
requestBody:
required: true
description: email - Email id <br> password - Password
content:
application/json:
schema:
type: object
required:
- email
- password
properties:
email:
type: string
password:
type: string
responses:
200:
description: User logged in successfully.
400:
description: Invalid input.
401:
description: Invalid email or password.
500:
description: Internal Server Error.
"""
user = User.get(email=data["email"])
if not user or not user.check_password(data["password"]):
return abort(
make_response(
jsonify(
error="Invalid email or password. Check again or register."
),
401,
)
)

return {
"access_token": create_access_token(identity=str(user.id), fresh=True),
"refresh_token": create_refresh_token(identity=str(user.id)),
}


@bp.route("/refresh")
class Refresh(MethodView):
"""Get new access token using your refresh token."""

@jwt_required(refresh=True)
@bp.response(200, AuthOut(only=("access_token",)))
def post(self):
"""
Get new access token using your refresh token
---
tags:
- User
description: Get new access token using your refresh token.
security:
- refresh_token: []
responses:
200:
description: New access token.
401:
description: Token expired, missing or invalid.
500:
description: Internal Server Error.
"""
identity = get_jwt_identity()
return {"access_token": create_access_token(identity=identity, fresh=False)}
File renamed without changes.
File renamed without changes.
File renamed without changes.
25 changes: 23 additions & 2 deletions app/schemas.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from marshmallow import Schema, ValidationError, fields, pre_load, validates
from marshmallow import Schema, ValidationError, fields, pre_load, validate, validates
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema, SQLAlchemySchema, auto_field

from app.models import Category, Product, Subcategory
from app.models import Category, Product, Subcategory, User


class CategoryOut(SQLAlchemyAutoSchema):
Expand Down Expand Up @@ -101,3 +101,24 @@ class NameArgs(Schema):

class PaginationArgs(Schema):
page = fields.Int(load_default=1)


# class AuthIn(Schema):
class AuthIn(SQLAlchemySchema):
class Meta:
model = User

# email validation handled in User model
email = auto_field()
password = fields.Str(required=True, validate=validate.Length(min=1))

@pre_load
def strip_strings(self, data, **kwargs):
if "email" in data and data["email"] is not None:
data["email"] = data["email"].strip()
return data


class AuthOut(Schema):
access_token = fields.Str()
refresh_token = fields.Str()
Loading