Prabhu Kiran Konda commited on
Commit
cc65344
1 Parent(s): 69e9ec0

Initial Commit

Browse files
Files changed (8) hide show
  1. Dockerfile +13 -0
  2. __init__.py +0 -0
  3. main.py +111 -0
  4. models.py +41 -0
  5. psql_database.py +29 -0
  6. requirements.txt +33 -0
  7. schemas.py +54 -0
  8. services.py +151 -0
Dockerfile ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+ WORKDIR /app
3
+ COPY requirements.txt ./requirements.txt
4
+ RUN apt-get update \
5
+ && apt-get -y install libpq-dev gcc \
6
+ && pip install psycopg2
7
+ # Install uvicorn
8
+ RUN pip install uvicorn
9
+ # Install dependencies
10
+ RUN pip install -r requirements.txt
11
+ COPY . /app
12
+ ENTRYPOINT ["uvicorn", "main:app"]
13
+ CMD ["--host", "0.0.0.0", "--port", "7860"]
__init__.py ADDED
File without changes
main.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException, security, Depends
2
+ import sqlalchemy.orm as orm
3
+ import services as services
4
+ import schemas as schemas
5
+ import models as models
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+
8
+
9
+ app = FastAPI(title="TODO API",
10
+ description='Todo App API using FastAPI, PostgreSQL and authentication support',
11
+ version='0.0.1')
12
+
13
+ app.add_middleware(
14
+ CORSMiddleware,
15
+ allow_origins=["*"],
16
+ allow_credentials=True,
17
+ allow_methods=["*"],
18
+ allow_headers=["*"],
19
+ )
20
+
21
+
22
+ @app.get("/")
23
+ async def root():
24
+ return {"Title": "Todo API",
25
+ "Description": "Todo App API using FastAPI, PostgreSQL and authentication support",
26
+ "Version": "0.0.1"}
27
+
28
+
29
+ # create user
30
+ @app.post("/api/users/")
31
+ async def create_user(user: schemas.userCreate,
32
+ db: orm.Session = Depends(services.get_db)):
33
+
34
+ db_user = await services.get_user_by_email(email=user.email.lower(), db=db)
35
+ if db_user:
36
+ raise HTTPException(status_code=400, detail="Email already registered")
37
+ # Await the create_user function
38
+ user = await services.create_user(user=user, db=db)
39
+ token = await services.create_token(user=user)
40
+ return token
41
+
42
+
43
+ @app.post("/api/token/")
44
+ async def generate_token(form_data: security.OAuth2PasswordRequestForm = Depends(),
45
+ db: orm.Session = Depends(services.get_db)):
46
+
47
+ user = await services.authenticate_user(form_data.username.lower(), form_data.password, db=db)
48
+ if not user:
49
+ raise HTTPException(
50
+ status_code=401, detail="Incorrect email or password")
51
+ token = await services.create_token(user=user)
52
+ return {
53
+ "access_token": token['access_token'],
54
+ "first_name": user.first_name,
55
+ "last_name": user.last_name,
56
+ }
57
+
58
+
59
+ @app.get('/api/users/me', response_model=schemas.User)
60
+ async def get_user(user: schemas.User = Depends(services.get_current_user)):
61
+ return user
62
+
63
+
64
+ @app.post("/api/users/todo")
65
+ async def create_todo(todo: schemas.TodoCreate, user: schemas.User = Depends(services.get_current_user), db: orm.Session = Depends(services.get_db)):
66
+ return await services.create_todo(user=user, todo=todo, db=db)
67
+
68
+
69
+ @app.get("/api/users/todo")
70
+ async def get_todos(user: schemas.User = Depends(services.get_current_user), db: orm.Session = Depends(services.get_db)):
71
+ return await services.get_todos(user=user, db=db)
72
+
73
+
74
+ @app.put("/api/users/todo/{todo_id}")
75
+ async def update_todo(todo_id: int,
76
+ todo: schemas.TodoCreate,
77
+ user: schemas.User = Depends(services.get_current_user),
78
+ db: orm.Session = Depends(services.get_db)):
79
+ return {
80
+ "todo": await services.update_todo(user=user, db=db, todo=todo, todo_id=todo_id),
81
+ "message": "Todo updated successfully"
82
+ }
83
+
84
+
85
+ @app.delete("/api/users/todo/{todo_id}")
86
+ async def delete_todo(todo_id: int,
87
+ user: schemas.User = Depends(services.get_current_user),
88
+ db: orm.Session = Depends(services.get_db)):
89
+ return {
90
+ "todo": await services.delete_todo(user=user, db=db, todo_id=todo_id),
91
+ "message": "Todo deleted successfully"
92
+ }
93
+
94
+
95
+ @app.get("/api/users/todo/today")
96
+ async def get_today_todos(user: schemas.User = Depends(services.get_current_user), db: orm.Session = Depends(services.get_db)):
97
+ return await services.get_today_todos(user=user, db=db)
98
+
99
+
100
+ @app.get("/api/users/todo/overdue")
101
+ async def get_overdue_todos(user: schemas.User = Depends(services.get_current_user), db: orm.Session = Depends(services.get_db)):
102
+ return await services.get_overdue_todos(user=user, db=db)
103
+
104
+
105
+ @app.get("/api/users/todo/upcoming")
106
+ async def get_upcoming_todos(user: schemas.User = Depends(services.get_current_user), db: orm.Session = Depends(services.get_db)):
107
+ return await services.get_upcoming_todos(user=user, db=db)
108
+
109
+ @app.get("/api/users/todo/completed")
110
+ async def get_completed_todos(user: schemas.User = Depends(services.get_current_user), db: orm.Session = Depends(services.get_db)):
111
+ return await services.get_completed_todos(user=user, db=db)
models.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy import create_engine, Column, Integer, String, Boolean, Date, ForeignKey, CheckConstraint
2
+ from sqlalchemy.ext.declarative import declarative_base
3
+ from sqlalchemy.orm import relationship
4
+ from psql_database import engine, Base
5
+ from passlib.hash import bcrypt
6
+
7
+ # Define the User model
8
+ class User(Base):
9
+ __tablename__ = 'users'
10
+ user_id = Column(Integer, primary_key=True)
11
+ first_name = Column(String(50), nullable=False)
12
+ last_name = Column(String(50), nullable=False)
13
+ email = Column(String(100), nullable=False, unique=True)
14
+ password = Column(String(100), nullable=False) # HASHED PASSWORD
15
+
16
+ todos = relationship('Todo', back_populates='user')
17
+
18
+
19
+ def verify_password(self, plain_password):
20
+ return bcrypt.verify(plain_password, self.password)
21
+
22
+
23
+
24
+ # Define the Todo model
25
+ class Todo(Base):
26
+ __tablename__ = 'todos'
27
+ todo_id = Column(Integer, primary_key=True)
28
+ user_id = Column(Integer, ForeignKey('users.user_id'), nullable=False)
29
+ task_name = Column(String(100), nullable=False)
30
+ task_description = Column(String)
31
+ priority = Column(Integer, CheckConstraint('priority >= 1 AND priority <= 3', name="priority should be either 1 or 2 or 3"), nullable=False) # 1: high, 2: medium, 3: low
32
+ category = Column(String(50))
33
+ due_date = Column(Date, nullable=False)
34
+ status = Column(Boolean, default=False)
35
+
36
+ user = relationship('User', back_populates='todos')
37
+
38
+ # Create the tables
39
+ def create_database_tables():
40
+ return Base.metadata.create_all(bind=engine)
41
+
psql_database.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import urllib.parse as up
3
+ import sqlalchemy as sa
4
+ from sqlalchemy.orm import sessionmaker
5
+ from sqlalchemy.ext.declarative import declarative_base
6
+
7
+ # Parse the DATABASE_URL using urllib.parse
8
+ up.uses_netloc.append("postgres")
9
+ url = up.urlparse(
10
+ "postgres://xphzyodo:[email protected]/xphzyodo")
11
+
12
+
13
+ # Create the connection string
14
+ conn_string = f'postgresql+psycopg2://{url.username}:{url.password}@{url.hostname}/{url.path[1:]}'
15
+
16
+ # Create the engine using the connection string
17
+ engine = sa.create_engine(conn_string)
18
+
19
+ # Reassign the engine to your existing engine variable
20
+ engine = engine
21
+
22
+ # Create the session factory
23
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
24
+
25
+ # Reassign the session factory to your existing SessionLocal variable
26
+ SessionLocal = SessionLocal
27
+
28
+ # Reassign the Base to your existing Base variable
29
+ Base = declarative_base()
requirements.txt ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ anyio==3.7.0
2
+ bcrypt==4.0.1
3
+ certifi==2023.5.7
4
+ cffi==1.15.1
5
+ charset-normalizer==3.1.0
6
+ click==8.1.3
7
+ cryptography==41.0.1
8
+ databases==0.7.0
9
+ docopt==0.6.2
10
+ exceptiongroup==1.1.1
11
+ fastapi==0.97.0
12
+ h11==0.14.0
13
+ idna==3.4
14
+ passlib==1.7.4
15
+ pip==23.1.2
16
+ pipreqs==0.4.13
17
+ psycopg2==2.9.4
18
+ psycopg2-binary==2.9.6
19
+ psycopg2cffi==2.9.0
20
+ pycparser==2.21
21
+ pydantic==1.10.9
22
+ PyJWT==2.7.0
23
+ python-multipart==0.0.6
24
+ requests==2.31.0
25
+ setuptools==58.0.4
26
+ six==1.16.0
27
+ sniffio==1.3.0
28
+ SQLAlchemy==1.4.48
29
+ starlette==0.27.0
30
+ typing_extensions==4.6.3
31
+ urllib3==2.0.3
32
+ uvicorn==0.22.0
33
+ yarg==0.1.9
schemas.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # why schemas? it is a way to define the structure of the data sent to the server and the data received from the server
2
+ from pydantic import BaseModel, Field
3
+ import datetime as dt
4
+
5
+ # template for user data. this is used to validate the data sent to the server
6
+
7
+
8
+ class userBase(BaseModel):
9
+ first_name: str = Field(...)
10
+ last_name: str = Field(...)
11
+ email: str = Field(...,)
12
+
13
+
14
+ class userCreate(userBase):
15
+ password: str = Field(...) # hashed password
16
+
17
+ class Config:
18
+ orm_mode = True # to tell pydantic to read the data even if it is not a dict but an ORM model
19
+ schema_extra = {
20
+ "example": {
21
+ "first_name": "John",
22
+ "last_name": "Doe",
23
+ "email": "[email protected]",
24
+ "password": "password",
25
+ }
26
+ }
27
+
28
+
29
+ class User(userBase):
30
+ user_id: int
31
+
32
+ class Config:
33
+ orm_mode = True
34
+
35
+
36
+ class TodoBase(BaseModel):
37
+ task_name: str
38
+ task_description: str
39
+ priority: int
40
+ category: str
41
+ due_date: dt.date
42
+ status: bool = False
43
+
44
+
45
+ class TodoCreate(TodoBase):
46
+ pass
47
+
48
+
49
+ class Todo(TodoBase):
50
+ todo_id: int
51
+ user_id: int
52
+
53
+ class Config:
54
+ orm_mode = True
services.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from copy import deepcopy
2
+ import models
3
+ from psql_database import engine, Base, SessionLocal
4
+ import sqlalchemy as sa
5
+ import schemas as schemas
6
+ import passlib.hash as pwd_hash
7
+ import re
8
+ from fastapi import HTTPException, Depends, security
9
+ import jwt
10
+
11
+
12
+ OAUTH2_SCHEME = security.OAuth2PasswordBearer(tokenUrl="/api/token")
13
+
14
+ JWT_SECRET = 'myjwtsecret'
15
+
16
+
17
+ def validate_email(email):
18
+ pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
19
+ return bool(re.match(pattern, email))
20
+
21
+
22
+ def create_database_tables():
23
+ return Base.metadata.create_all(bind=engine)
24
+
25
+
26
+ def get_db():
27
+ db = SessionLocal()
28
+ try:
29
+ yield db
30
+ finally:
31
+ db.close()
32
+
33
+
34
+ async def get_user_by_email(email: str, db: sa.orm.Session):
35
+ return db.query(models.User).filter(models.User.email == email).first()
36
+
37
+
38
+ async def create_user(user: schemas.userCreate, db: sa.orm.Session):
39
+
40
+ if not validate_email(user.email):
41
+ raise HTTPException(status_code=400, detail="Invalid email")
42
+
43
+ user_obj = models.User(
44
+ first_name=user.first_name,
45
+ last_name=user.last_name,
46
+ email=user.email,
47
+ password=pwd_hash.bcrypt.hash(user.password)
48
+ )
49
+
50
+ db.add(user_obj)
51
+ db.commit() # commit operation
52
+ db.refresh(user_obj) # Await the refresh operation
53
+ return user_obj
54
+
55
+
56
+ async def authenticate_user(email, password, db: sa.orm.Session):
57
+ user = await get_user_by_email(email=email, db=db)
58
+
59
+ if not user:
60
+ raise HTTPException(status_code=404, detail="User not found")
61
+ return user if user.verify_password(password) else False
62
+
63
+
64
+ async def create_token(user: models.User):
65
+ user_dict = {
66
+ "first_name": user.first_name,
67
+ "last_name": user.last_name,
68
+ "email": user.email,
69
+ }
70
+ token = jwt.encode(user_dict, JWT_SECRET)
71
+
72
+ return dict(access_token=token, token_type='bearer', status='success')
73
+
74
+
75
+ async def get_current_user(db: sa.orm.Session = Depends(get_db), token: str = Depends(OAUTH2_SCHEME)):
76
+ try:
77
+ payload = jwt.decode(token, JWT_SECRET, algorithms=['HS256'])
78
+ print(payload)
79
+ user = db.query(models.User).filter(
80
+ models.User.email == payload['email']).first()
81
+ if not user:
82
+ raise HTTPException(status_code=404, detail="User not found")
83
+ return schemas.User(**user.__dict__)
84
+ except Exception as e:
85
+ raise HTTPException(status_code=401, detail="Invalid token") from e
86
+
87
+
88
+ async def create_todo(user: schemas.User, todo: schemas.TodoCreate, db: sa.orm.Session = Depends(get_db)):
89
+ print(todo.dict())
90
+
91
+ todo_obj = models.Todo(
92
+ task_name=todo.task_name,
93
+ task_description=todo.task_description,
94
+ priority=todo.priority,
95
+ category=todo.category,
96
+ due_date=todo.due_date,
97
+ status=todo.status,
98
+ user_id=user.user_id,
99
+ )
100
+
101
+ db.add(todo_obj)
102
+ db.commit()
103
+ db.refresh(todo_obj)
104
+ return todo_obj
105
+
106
+
107
+ async def get_todos(user: schemas.User, db: sa.orm.Session = Depends(get_db)):
108
+ return db.query(models.Todo).filter(models.Todo.user_id == user.user_id).all()
109
+
110
+
111
+ async def delete_todo(todo_id: int, user: schemas.User, db: sa.orm.Session = Depends(get_db)):
112
+ todo = db.query(models.Todo).filter(models.Todo.user_id ==
113
+ user.user_id, models.Todo.todo_id == todo_id).first()
114
+ if not todo:
115
+ raise HTTPException(status_code=404, detail="Todo not found")
116
+ db.delete(todo)
117
+ db.commit()
118
+ return todo
119
+
120
+
121
+ async def update_todo(todo_id: int, user: schemas.User, todo: schemas.TodoCreate, db: sa.orm.Session = Depends(get_db)):
122
+ todo_db = db.query(models.Todo).filter(
123
+ models.Todo.user_id == user.user_id, models.Todo.todo_id == todo_id).first()
124
+ if not todo_db:
125
+ raise HTTPException(status_code=404, detail="Todo not found")
126
+ todo_db.task_name = todo.task_name
127
+ todo_db.task_description = todo.task_description or todo.task_description
128
+ todo_db.priority = todo.priority
129
+ todo_db.category = todo.category
130
+ todo_db.due_date = todo.due_date
131
+ todo_db.status = todo.status
132
+
133
+ db.commit()
134
+ db.refresh(todo_db)
135
+ return todo_db
136
+
137
+
138
+ async def get_today_todos(user: schemas.User, db: sa.orm.Session = Depends(get_db)):
139
+ return db.query(models.Todo).filter(models.Todo.user_id == user.user_id, sa.func.date(models.Todo.due_date) == sa.func.date(sa.func.now())).all()
140
+
141
+
142
+ async def get_overdue_todos(user: schemas.User, db: sa.orm.Session = Depends(get_db)):
143
+ return db.query(models.Todo).filter(models.Todo.user_id == user.user_id, models.Todo.due_date < sa.func.now()).all()
144
+
145
+
146
+ async def get_upcoming_todos(user: schemas.User, db: sa.orm.Session = Depends(get_db)):
147
+ return db.query(models.Todo).filter(models.Todo.user_id == user.user_id, models.Todo.due_date > sa.func.now()).all()
148
+
149
+
150
+ async def get_completed_todos(user: schemas.User, db: sa.orm.Session = Depends(get_db)):
151
+ return db.query(models.Todo).filter(models.Todo.user_id == user.user_id, models.Todo.status == True).all()