Mbonea commited on
Commit
b856986
·
1 Parent(s): 5ac4c8b

new age with tortole user works but not tests

Browse files
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ *.pyc
2
+ *sqlite3*
.gitpod.yml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ # This configuration file was automatically generated by Gitpod.
2
+ # Please adjust to your needs (see https://www.gitpod.io/docs/introduction/learn-gitpod/gitpod-yaml)
3
+ # and commit this file to your remote git repository to share the goodness with others.
4
+
5
+ # Learn more from ready-to-use templates: https://www.gitpod.io/docs/introduction/getting-started/quickstart
6
+
7
+ tasks:
8
+ - init: pip install -r requirements.txt
9
+
10
+
App/Analytics/{Model.py → Model__.py} RENAMED
File without changes
App/CommentLikes/CommentLikesRoutes.py DELETED
@@ -1,12 +0,0 @@
1
- from fastapi import APIRouter, status
2
- from .Schemas import BaseRequest, editRequest
3
- from .Model import Comments
4
- from App.Users.Model import User
5
- from App.Post.Model import Post
6
-
7
- commentLikes_router = APIRouter(tags=["CommentLikes"])
8
-
9
-
10
- @commentLikes_router.get("/likes/comments/add")
11
- async def add_commentLike():
12
- pass
 
 
 
 
 
 
 
 
 
 
 
 
 
App/CommentLikes/Model.py DELETED
@@ -1,19 +0,0 @@
1
- import asyncio
2
- import orm
3
- import psycopg2
4
- import datetime
5
- import pydantic
6
- from App.modelInit import database, models
7
- from App.Users.Model import User
8
- from App.Post.Model import Post
9
-
10
-
11
- class CommentLike(orm.Model):
12
- tablename = "commentlikes"
13
- registry = models
14
- fields = {
15
- "id": orm.Integer(primary_key=True),
16
- "user": orm.ForeignKey(User, on_delete=orm.CASCADE),
17
- "post": orm.ForeignKey(Post, on_delete=orm.CASCADE),
18
- "createdAt": orm.DateTime(index=True, default=datetime.datetime.now),
19
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
App/CommentLikes/Schemas.py DELETED
@@ -1,14 +0,0 @@
1
- from typing import List, Optional
2
- from pydantic import EmailStr, BaseModel
3
- from datetime import date, datetime, time, timedelta
4
-
5
-
6
- class addLike(BaseModel):
7
- postId: int
8
- userId: int
9
-
10
-
11
- class deleteLike(BaseModel):
12
- id: int
13
- postId: int
14
- userId: int
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
App/Comments/CommentRoutes.py DELETED
@@ -1,51 +0,0 @@
1
- from fastapi import APIRouter, status
2
- from .Schemas import createComment, editComment, deleteComment, allComments
3
- from .Model import Comment
4
- from App.Users.Model import User
5
- from App.Post.Model import Post
6
- from App.utils import get_user_and_post
7
- import asyncio
8
-
9
- postLike_router = APIRouter(tags=["PostLikes"])
10
-
11
-
12
- comment_router = APIRouter(tags=["Comments"])
13
-
14
-
15
- @comment_router.post("/comment/create")
16
- async def create_comment(comment: createComment):
17
- user, _post = await get_user_and_post(comment)
18
- data = await Comment.objects.create(user=user, content=comment.content, post=_post)
19
- return {"code": 200, "message": "success", "payload": data.__dict__}
20
-
21
-
22
- @comment_router.post("/comment/edit")
23
- async def edit_comment(comment: editComment):
24
- # user,_post = await get_user_and_post(comment)
25
- db_comment = await Comment.objects.filter(id=comment.id).first()
26
- if not db_comment:
27
- return {"code": 400, "message": "Comment does not exist", "payload": None}
28
- if db_comment.user.id != comment.userId:
29
- return {
30
- "code": 400,
31
- "message": "This comment belongs to a different user",
32
- "payload": None,
33
- }
34
- db_data = await db_comment.update(content=comment.content)
35
- return {"code": 200, "message": "success", "payload": None}
36
-
37
-
38
- @comment_router.post("/comment/all")
39
- async def all_comments(comment: allComments):
40
- user = await User.objects.filter(id=comment.userId).first()
41
- if not user:
42
- return {"code": 400, "message": "User does not exist", "payload": None}
43
- db_comment = await Comment.objects.filter(user=user).all()
44
-
45
- if not db_comment:
46
- return {"code": 400, "message": "Comment does not exist", "payload": None}
47
- return {
48
- "code": 200,
49
- "message": "success",
50
- "payload": [i.__dict__ for i in db_comment],
51
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
App/Comments/Model.py DELETED
@@ -1,21 +0,0 @@
1
- import asyncio
2
- import orm
3
- import psycopg2
4
- import datetime
5
- import pydantic
6
- from App.modelInit import database, models
7
- from App.Users.Model import User
8
-
9
-
10
- class Comment(orm.Model):
11
- tablename = "comments"
12
- registry = models
13
- fields = {
14
- "id": orm.Integer(primary_key=True),
15
- "user": orm.ForeignKey(User, on_delete=orm.CASCADE),
16
- "post": orm.ForeignKey(User, on_delete=orm.CASCADE),
17
- "content": orm.String(max_length=100),
18
- "likes": orm.Integer(index=True, default=0),
19
- "createdAt": orm.DateTime(index=True, default=datetime.datetime.now),
20
- "updatedAt": orm.DateTime(index=True, default=datetime.datetime.now),
21
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
App/Comments/Schemas.py DELETED
@@ -1,26 +0,0 @@
1
- from typing import List, Optional
2
- from pydantic import EmailStr, BaseModel
3
- from datetime import date, datetime, time, timedelta
4
-
5
-
6
- class createComment(BaseModel):
7
- userId: int
8
- content: str
9
- postId: int
10
-
11
-
12
- class editComment(BaseModel):
13
- id: int
14
- userId: int
15
- content: str
16
- postId: int
17
-
18
-
19
- class deleteComment(BaseModel):
20
- id: int
21
- userId: int
22
- postId: int
23
-
24
-
25
- class allComments(BaseModel):
26
- userId: int
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
App/Comments/__pycache__/CommentRoutes.cpython-38.pyc DELETED
Binary file (1.87 kB)
 
App/Comments/__pycache__/Model.cpython-38.pyc DELETED
Binary file (860 Bytes)
 
App/Comments/__pycache__/Schemas.cpython-38.pyc DELETED
Binary file (1.1 kB)
 
App/Post/Model.py DELETED
@@ -1,19 +0,0 @@
1
- import asyncio
2
- import orm
3
- import psycopg2
4
- import datetime
5
- import pydantic
6
- from App.modelInit import database, models
7
- from App.Users.Model import User
8
-
9
-
10
- class Post(orm.Model):
11
- tablename = "posts"
12
- registry = models
13
- fields = {
14
- "id": orm.Integer(primary_key=True),
15
- "pageId": orm.Integer(index=True, default=0),
16
- "content": orm.JSON(),
17
- "recommendations": orm.JSON(allow_null=True),
18
- "createdAt": orm.DateTime(index=True, default=datetime.datetime.now),
19
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
App/Post/PostRoutes.py DELETED
@@ -1,30 +0,0 @@
1
- from fastapi import APIRouter, status
2
- from .Schemas import createPost, editPost, getPost
3
- from .Model import Post
4
-
5
- post_router = APIRouter(tags=["Posts"])
6
-
7
-
8
- @post_router.post("/post/create")
9
- async def create_post(post: createPost):
10
- data = await Post.objects.create(**post.dict())
11
- return {"code": 200, "message": "success", "payload": data.__dict__}
12
-
13
-
14
- @post_router.post("/post/update")
15
- async def create_post(post: editPost):
16
- temp = await Post.objects.get(id=post.id)
17
- data = await temp.update(recommendations=post.recommendations, content=post.content)
18
- # data=await Post.objects.update(**post.dict())
19
- return {"code": 200, "message": "success", "payload": temp.__dict__}
20
-
21
-
22
- @post_router.post("/post/get")
23
- async def create_post(post: getPost):
24
- data = await Post.objects.get(id=post.id)
25
- return {"code": 200, "message": "success", "payload": data.__dict__}
26
-
27
- @post_router.post("/post/delete_all")
28
- async def create_post():
29
- data = await Post.objects.delete()
30
- return {"code": 200, "message": "success", "payload": data.__dict__}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
App/Post/Schemas.py DELETED
@@ -1,19 +0,0 @@
1
- from typing import List, Optional
2
- from pydantic import EmailStr, BaseModel
3
- from datetime import date, datetime, time, timedelta
4
-
5
-
6
- class editPost(BaseModel):
7
- id: Optional[int]
8
- content: Optional[dict]
9
- recommendations: Optional[dict]
10
-
11
-
12
- class createPost(BaseModel):
13
- pageId:int
14
- content: Optional[dict]
15
- recommendations: Optional[dict]
16
-
17
-
18
- class getPost(BaseModel):
19
- id: Optional[int]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
App/Post/__pycache__/Model.cpython-38.pyc DELETED
Binary file (757 Bytes)
 
App/Post/__pycache__/PostRoutes.cpython-38.pyc DELETED
Binary file (1.1 kB)
 
App/Post/__pycache__/Schemas.cpython-38.pyc DELETED
Binary file (947 Bytes)
 
App/PostLikes/Model.py DELETED
@@ -1,19 +0,0 @@
1
- import asyncio
2
- import orm
3
- import psycopg2
4
- import datetime
5
- import pydantic
6
- from App.modelInit import database, models
7
- from App.Users.Model import User
8
- from App.Post.Model import Post
9
-
10
-
11
- class PostLike(orm.Model):
12
- tablename = "postlikes"
13
- registry = models
14
- fields = {
15
- "id": orm.Integer(primary_key=True),
16
- "user": orm.ForeignKey(User, on_delete=orm.CASCADE),
17
- "post": orm.ForeignKey(Post, on_delete=orm.CASCADE),
18
- "createdAt": orm.DateTime(index=True, default=datetime.datetime.now),
19
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
App/PostLikes/PostLikesRoutes.py DELETED
@@ -1,43 +0,0 @@
1
- from fastapi import APIRouter, Depends
2
- from .Schemas import addLike, deleteLike
3
- from .Model import PostLike
4
- from App.Users.Model import User
5
- from App.Post.Model import Post
6
- from App.utils import get_user_and_post
7
- import asyncio
8
-
9
- postLike_router = APIRouter(tags=["PostLikes"])
10
-
11
-
12
- @postLike_router.post("/postLike/add")
13
- async def add_postLike(post: addLike):
14
- user, _post = await get_user_and_post(
15
- post
16
- ) # validate if both the post and user exist
17
- previos = await PostLike.objects.filter(user=user).filter(post=_post).first()
18
- if previos:
19
- return {"code": 400, "message": "you already liked it", "payload": None}
20
-
21
- data = await PostLike.objects.create(post=_post, user=user)
22
- return {"code": 200, "message": "success", "payload": data.__dict__}
23
-
24
-
25
- @postLike_router.post("/postLike/delete")
26
- async def create_post(post: deleteLike):
27
- user, _post = await get_user_and_post(post)
28
- data = await PostLike.objects.filter(id=post.id).first()
29
- if not data:
30
- return {"code": 400, "message": "Does not exist", "payload": None}
31
- if user.id == data.user.id and _post.id == data.post.id:
32
- await data.delete()
33
- return {"code": 200, "message": "success", "payload": None}
34
-
35
-
36
- @postLike_router.post("/postLike/get")
37
- async def create_post(post: deleteLike):
38
- user, _post = await get_user_and_post(post)
39
- data = await PostLike.objects.filter(id=post.id).first()
40
- if not data:
41
- return {"code": 400, "message": "Does not exist", "payload": None}
42
- if user.id == data.user.id and _post.id == data.post.user.id:
43
- return {"code": 200, "message": "success", "payload": data.__dict__}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
App/PostLikes/Schemas.py DELETED
@@ -1,14 +0,0 @@
1
- from typing import List, Optional
2
- from pydantic import EmailStr, BaseModel
3
- from datetime import date, datetime, time, timedelta
4
-
5
-
6
- class addLike(BaseModel):
7
- postId: int
8
- userId: int
9
-
10
-
11
- class deleteLike(BaseModel):
12
- id: int
13
- postId: int
14
- userId: int
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
App/PostLikes/__pycache__/Model.cpython-38.pyc DELETED
Binary file (808 Bytes)
 
App/PostLikes/__pycache__/PostLikesRoutes.cpython-38.pyc DELETED
Binary file (1.6 kB)
 
App/PostLikes/__pycache__/Schemas.cpython-38.pyc DELETED
Binary file (714 Bytes)
 
App/Tests/test_user_routes.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+ from httpx import AsyncClient
3
+ from App.app import app # Assuming app.py initializes and configures the FastAPI instance
4
+
5
+ # Define test user data
6
+ test_user = {
7
+ "name": "Testasd User",
8
+ "email": "[email protected]",
9
+ "password": "strongasdpassword123",
10
+ "phoneNumber": "1234897890",
11
+ "mac_address": "strdding"}
12
+
13
+ reset_password_data = {
14
+ "phoneNumber": "1234897890",
15
+ "new_password": "newpassword123"
16
+ }
17
+
18
+ @pytest.mark.asyncio
19
+ async def test_register_user():
20
+ async with AsyncClient(app=app, base_url="https://8000-mboneaewall-templatebac-t1vqae3xohv.ws-eu116.gitpod.io/") as client:
21
+ response = await client.post("/user/register", json=test_user)
22
+ assert response.status_code == 201
23
+ assert response.json()["code"] == 200
24
+ assert response.json()["message"] == "User created successfully"
25
+
26
+ @pytest.mark.asyncio
27
+ async def test_login_user():
28
+ async with AsyncClient(app=app, base_url="https://8000-mboneaewall-templatebac-t1vqae3xohv.ws-eu116.gitpod.io/") as client:
29
+ login_data = {"phoneNumber": test_user["phoneNumber"], "password": test_user["password"],"mac_address": "strdding"}
30
+ response = await client.post("/user/login", data=login_data)
31
+ assert response.status_code == 200
32
+ assert "access_token" in response.json()
33
+
34
+ @pytest.mark.asyncio
35
+ async def test_forgot_password():
36
+ async with AsyncClient(app=app, base_url="https://8000-mboneaewall-templatebac-t1vqae3xohv.ws-eu116.gitpod.io/") as client:
37
+ response = await client.post("/user/forgot-password", json={"phoneNumber": test_user["phoneNumber"]})
38
+ assert response.status_code == 200
39
+ assert response.json()["code"] == 200
40
+ assert response.json()["message"] == "Password reset token sent. Check your phone for further instructions."
41
+
42
+ @pytest.mark.asyncio
43
+ async def test_verify_reset_token():
44
+ async with AsyncClient(app=app, base_url="https://8000-mboneaewall-templatebac-t1vqae3xohv.ws-eu116.gitpod.io/") as client:
45
+ # Mock the reset token for testing (this would typically be retrieved from the response)
46
+ verify_data = {"phoneNumber": test_user["phoneNumber"], "reset_token": "123456"}
47
+ response = await client.post("/user/verify-reset-token", json=verify_data)
48
+ assert response.status_code == 200
49
+ assert response.json()["code"] == 200
50
+ assert response.json()["message"] == "Token verified. You may now reset your password."
51
+
52
+ @pytest.mark.asyncio
53
+ async def test_reset_password():
54
+ async with AsyncClient(app=app, base_url="https://8000-mboneaewall-templatebac-t1vqae3xohv.ws-eu116.gitpod.io/") as client:
55
+ response = await client.post("/user/reset-password", json=reset_password_data)
56
+ assert response.status_code == 200
57
+ assert response.json()["code"] == 200
58
+ assert response.json()["message"] == "Password has been reset successfully."
App/Users/Model.py CHANGED
@@ -1,28 +1,76 @@
1
  import asyncio
2
- import orm
3
- import psycopg2
4
- import datetime
5
- import pydantic
6
  from passlib.context import CryptContext
7
- from App.modelInit import database, models
8
-
 
9
  pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
- class User(orm.Model):
13
- tablename = "users"
14
- registry = models
15
- fields = {
16
- "id": orm.Integer(primary_key=True),
17
- "name": orm.String(max_length=100, index=True),
18
- "email": orm.String(max_length=100, index=True, unique=True),
19
- "password": orm.String(max_length=100, index=True),
20
- "phoneNumber": orm.String(max_length=100, index=True, allow_null=True),
21
- "account_type": orm.Integer(index=True, default=1),
22
- "createdAt": orm.DateTime(index=True, default=datetime.datetime.now),
23
- "updatedAt": orm.DateTime(index=True, default=datetime.datetime.now),
24
- "lastLogin": orm.DateTime(index=True, default=datetime.datetime.now),
25
- }
26
-
27
- def verify_password(self, plain_password):
28
- return pwd_context.verify(plain_password, self.password)
 
1
  import asyncio
2
+ import uuid
3
+ from tortoise import fields, Tortoise
4
+ from tortoise.models import Model
 
5
  from passlib.context import CryptContext
6
+ import datetime
7
+ import random
8
+ import string
9
  pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
10
 
11
+ def generate_short_uuid() -> str:
12
+ """Generate a random 5-character alphanumeric string."""
13
+ return ''.join(random.choices(string.ascii_letters + string.digits, k=5))
14
+
15
+
16
+ class User(Model):
17
+ id = fields.CharField(primary_key=True, max_length=5, default=generate_short_uuid)
18
+ name = fields.CharField(max_length=100, db_index=True)
19
+ email = fields.CharField(max_length=100, db_index=True, unique=True)
20
+ password = fields.CharField(max_length=100)
21
+ phoneNumber = fields.CharField(max_length=100, null=True)
22
+ account_type = fields.IntField(default=1)
23
+ balance = fields.DecimalField(max_digits=10, decimal_places=2, default=0.00)
24
+ ip_address = fields.CharField(max_length=45, null=True)
25
+ mac_address = fields.CharField(max_length=17, null=True)
26
+ createdAt = fields.DatetimeField(default=datetime.datetime.now)
27
+ updatedAt = fields.DatetimeField(default=datetime.datetime.now)
28
+ lastLogin = fields.DatetimeField(default=datetime.datetime.now)
29
+ failed_attempts = fields.IntField(default=0)
30
+ account_locked = fields.BooleanField(default=False)
31
+ reset_token = fields.CharField(max_length=100, null=True, unique=True)
32
+ reset_token_expiration = fields.DatetimeField(null=True)
33
+
34
+ class Meta:
35
+ table = "users"
36
+
37
+ def verify_password(self, plain_password: str) -> bool:
38
+ if self.account_locked:
39
+ print("Account is locked due to too many failed attempts.")
40
+ return False
41
+
42
+ if pwd_context.verify(plain_password, self.password):
43
+ self.failed_attempts = 0 # Reset failed attempts on success
44
+ self.account_locked = False
45
+ self.save() # Save changes to reset the failed attempts count
46
+ return True
47
+ else:
48
+ self.failed_attempts += 1
49
+ if self.failed_attempts >= 5:
50
+ self.account_locked = True
51
+ print("Account locked due to too many failed attempts.")
52
+ self.save() # Save changes to update the failed attempts count
53
+ return False
54
+
55
+ async def initiate_password_reset(self):
56
+ # Generate a unique reset token
57
+ self.reset_token = str(uuid.uuid4())
58
+ self.reset_token_expiration = datetime.datetime.now() + datetime.timedelta(hours=1) # Token expires in 1 hour
59
+ await self.save()
60
+
61
+ # In a real application, send this token to the user's email or phone
62
+ print(f"Password reset token for {self.email}: {self.reset_token}")
63
+
64
+ async def reset_password(self, reset_token, new_password):
65
+ # Check if the reset token is valid and not expired
66
+ if self.reset_token != reset_token or datetime.datetime.now() > self.reset_token_expiration:
67
+ print("Invalid or expired reset token.")
68
+ return False
69
 
70
+ # Set the new password
71
+ self.password = pwd_context.hash(new_password)
72
+ self.reset_token = None # Clear the token after successful reset
73
+ self.reset_token_expiration = None
74
+ await self.save()
75
+ print("Password has been reset successfully.")
76
+ return True
 
 
 
 
 
 
 
 
 
 
App/Users/Schemas.py CHANGED
@@ -1,15 +1,99 @@
1
- from typing import List, Optional
2
- from pydantic import EmailStr, BaseModel
 
3
  from passlib.context import CryptContext
4
 
5
  pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
6
 
7
 
8
- class BaseRequest(BaseModel):
9
- email: EmailStr
10
- name: str
11
- password: str
12
- phoneNumber: Optional[str]
13
-
 
14
  def hash_password(self):
15
  self.password = pwd_context.hash(self.password)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field, constr
2
+ from typing import Optional
3
+ from datetime import datetime
4
  from passlib.context import CryptContext
5
 
6
  pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
7
 
8
 
9
+ # Register User Request
10
+ class RegisterUserRequest(BaseModel):
11
+ name: str = Field(..., max_length=100)
12
+ email: str = Field(..., max_length=100)
13
+ password: constr(min_length=8)
14
+ phoneNumber: str = Field(..., max_length=15)
15
+ mac_address: str = Field(..., max_length=17) # Standard MAC address format
16
  def hash_password(self):
17
  self.password = pwd_context.hash(self.password)
18
+ class Config:
19
+ schema_extra = {
20
+ "example": {
21
+ "name": "John Doe",
22
+ "email": "[email protected]",
23
+ "password": "strongpassword123",
24
+ "phoneNumber": "1234567890",
25
+ "mac_address": "00:1A:2B:3C:4D:5E"
26
+ }
27
+ }
28
+
29
+
30
+ # Login User Request (using phone number and MAC address)
31
+ class LoginUserRequest(BaseModel):
32
+ phoneNumber: str = Field(..., max_length=15)
33
+ password: constr(min_length=8)
34
+ mac_address: str = Field(..., max_length=17) # Required for login
35
+
36
+ class Config:
37
+ schema_extra = {
38
+ "example": {
39
+ "phoneNumber": "1234567890",
40
+ "password": "strongpassword123",
41
+ "mac_address": "00:1A:2B:3C:4D:5E"
42
+ }
43
+ }
44
+
45
+
46
+ # Access Token Response (used for Login)
47
+ class AccessTokenResponse(BaseModel):
48
+ access_token: str
49
+ token_type: str = "bearer"
50
+
51
+
52
+ # Base Response Schema
53
+ class BaseResponse(BaseModel):
54
+ code: int
55
+ message: str
56
+ payload: Optional[dict] = None
57
+
58
+ # Schemas.py
59
+
60
+
61
+
62
+ # Step 1: Forgot Password Request (using phone number)
63
+ class ForgotPasswordRequest(BaseModel):
64
+ phoneNumber: str = Field(..., max_length=15)
65
+
66
+ class Config:
67
+ schema_extra = {
68
+ "example": {
69
+ "phoneNumber": "1234567890"
70
+ }
71
+ }
72
+
73
+
74
+ # Step 2a: Verify Reset Token Request
75
+ class VerifyResetTokenRequest(BaseModel):
76
+ phoneNumber: str = Field(..., max_length=15)
77
+ reset_token: str = Field(..., max_length=6) # Short token
78
+
79
+ class Config:
80
+ schema_extra = {
81
+ "example": {
82
+ "phoneNumber": "1234567890",
83
+ "reset_token": "123456"
84
+ }
85
+ }
86
+
87
+
88
+ # Step 2b: Reset Password Request (after token verification)
89
+ class ResetPasswordRequest(BaseModel):
90
+ phoneNumber: str = Field(..., max_length=15)
91
+ new_password: constr(min_length=8)
92
+
93
+ class Config:
94
+ schema_extra = {
95
+ "example": {
96
+ "phoneNumber": "1234567890",
97
+ "new_password": "newstrongpassword123"
98
+ }
99
+ }
App/Users/UserRoutes.py CHANGED
@@ -1,28 +1,105 @@
1
- from fastapi import APIRouter, status
2
- from .Schemas import BaseRequest
 
 
 
3
  from .Model import User
4
- from sqlalchemy import and_
5
- from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
 
 
6
 
7
- oauth_scheme = OAuth2PasswordBearer(tokenUrl="/user/login")
 
 
 
 
 
 
8
  user_router = APIRouter(tags=["User"])
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
- @user_router.post("/user/register")
12
- async def register_user(user: BaseRequest):
13
- data = await User.objects.filter(email=user.email).first()
14
- if data != None:
15
- return {"code": 400, "message": "user exists", "payload": None}
16
- else:
17
- user.hash_password()
18
- sample = await User.objects.create(**user.dict())
19
- return {"code": 200, "message": "success", "payload": None}
20
-
21
-
22
- @user_router.post("/user/login")
23
- async def register_user(user: BaseRequest):
24
- db_user = await User.objects.filter(email=user.email).first()
25
- if db_user:
26
- if db_user.verify_password(user.password):
27
- return {"code": 200, "message": "success", "payload": db_user.__dict__}
28
- return {"code": 401, "message": "Invalid Credentials", "payload": None}
 
1
+ from fastapi import APIRouter, HTTPException, status
2
+ from .Schemas import (
3
+ RegisterUserRequest, ResetPasswordRequest, LoginUserRequest,
4
+ AccessTokenResponse, BaseResponse, ForgotPasswordRequest, VerifyResetTokenRequest
5
+ )
6
  from .Model import User
7
+ from jose import jwt
8
+ from datetime import datetime, timedelta,timezone
9
+ import random
10
+ from passlib.context import CryptContext
11
 
12
+ # Configurations for JWT and Password Reset
13
+ SECRET_KEY = "your_secret_key_here"
14
+ ALGORITHM = "HS256"
15
+ ACCESS_TOKEN_EXPIRE_MINUTES = 30
16
+ PASSWORD_RESET_EXPIRE_MINUTES = 15 # Reset token valid for 15 minutes
17
+
18
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
19
  user_router = APIRouter(tags=["User"])
20
 
21
+ # Utility function to create JWT token
22
+ def create_access_token(data: dict, expires_delta: timedelta = None):
23
+ to_encode = data.copy()
24
+ expire = datetime.utcnow() + (expires_delta or timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES))
25
+ to_encode.update({"exp": expire})
26
+ return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
27
+
28
+ # Register route
29
+ @user_router.post("/user/register", response_model=BaseResponse, status_code=status.HTTP_201_CREATED)
30
+ async def register_user(user: RegisterUserRequest):
31
+ existing_user = await User.filter(phoneNumber=user.phoneNumber).first()
32
+ if existing_user:
33
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="User already exists.")
34
+
35
+ user.hash_password()
36
+ new_user = await User.create(**user.dict())
37
+ return BaseResponse(code=200, message="User created successfully", payload={"user_id": new_user.id})
38
+
39
+ # Login route (using phone number)
40
+ @user_router.post("/user/login", response_model=AccessTokenResponse, status_code=status.HTTP_200_OK)
41
+ async def login_user(user: LoginUserRequest):
42
+ db_user = await User.filter(phoneNumber=user.phoneNumber).first()
43
+ if db_user and db_user.verify_password(user.password):
44
+ access_token = create_access_token(data={"sub": db_user.phoneNumber})
45
+ return AccessTokenResponse(access_token=access_token, token_type="bearer")
46
+
47
+ raise HTTPException(
48
+ status_code=status.HTTP_401_UNAUTHORIZED,
49
+ detail="Invalid credentials",
50
+ headers={"WWW-Authenticate": "Bearer"},
51
+ )
52
+
53
+ # Forgot Password route (using phone number only)
54
+ @user_router.post("/user/forgot-password", response_model=BaseResponse, status_code=status.HTTP_200_OK)
55
+ async def forgot_password(request: ForgotPasswordRequest):
56
+ user = await User.filter(phoneNumber=request.phoneNumber).first()
57
+ if not user:
58
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found.")
59
+
60
+ # Generate a short reset token (6-digit code)
61
+ reset_token = f"{random.randint(100000, 999999)}"
62
+ reset_token_expiration = datetime.utcnow() + timedelta(minutes=PASSWORD_RESET_EXPIRE_MINUTES)
63
+
64
+ # Store reset token and expiration in the user record
65
+ user.reset_token = reset_token
66
+ user.reset_token_expiration = reset_token_expiration
67
+ await user.save()
68
+
69
+ # In production, send this token via SMS to the user's phone number
70
+ print(f"Password reset token for {request.phoneNumber}: {reset_token}")
71
+
72
+ return BaseResponse(code=200, message="Password reset token sent. Check your phone for further instructions.")
73
+
74
+ # Verify Reset Token route
75
+ @user_router.post("/user/verify-reset-token", response_model=BaseResponse, status_code=status.HTTP_200_OK)
76
+ async def verify_reset_token(request: VerifyResetTokenRequest):
77
+ user = await User.filter(phoneNumber=request.phoneNumber, reset_token=request.reset_token).first()
78
+ if not user:
79
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Invalid token or phone number.")
80
+
81
+ # Check if the reset token has expired
82
+ if datetime.utcnow().replace(tzinfo=timezone.utc) > user.reset_token_expiration:
83
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Reset token has expired.")
84
+
85
+ return BaseResponse(code=200, message="Token verified. You may now reset your password.")
86
+
87
+ # Reset Password route (After Token Verification)
88
+ @user_router.post("/user/reset-password", response_model=BaseResponse, status_code=status.HTTP_200_OK)
89
+ async def reset_password(request: ResetPasswordRequest):
90
+ user = await User.filter(phoneNumber=request.phoneNumber).first()
91
+ if not user:
92
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found.")
93
+
94
+ # Check if the reset token is present and not expired
95
+
96
+ if not user.reset_token or datetime.utcnow().replace(tzinfo=timezone.utc) > user.reset_token_expiration:
97
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Reset token invalid or expired.")
98
+
99
+ # Update the user's password and clear the reset token
100
+ user.password = pwd_context.hash(request.new_password)
101
+ user.reset_token = None
102
+ user.reset_token_expiration = None
103
+ await user.save()
104
 
105
+ return BaseResponse(code=200, message="Password has been reset successfully.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
App/app.py CHANGED
@@ -1,19 +1,15 @@
1
  from fastapi import FastAPI
 
2
  from .Users.UserRoutes import user_router
3
- from .Post.PostRoutes import post_router
4
- from .PostLikes.PostLikesRoutes import postLike_router
5
- from .Comments.CommentRoutes import comment_router
6
- from .modelInit import models, database
7
 
8
  app = FastAPI()
9
 
10
 
11
  @app.on_event("startup")
12
  async def startup_event():
13
- await models.create_all()
14
- if not database.is_connected:
15
- await database.connect()
16
- print("connected!")
17
 
18
 
19
  @app.get("/")
@@ -22,9 +18,7 @@ async def landing_page():
22
 
23
 
24
  app.include_router(user_router)
25
- app.include_router(comment_router)
26
- app.include_router(post_router)
27
- app.include_router(postLike_router)
28
 
29
 
30
  async def main():
 
1
  from fastapi import FastAPI
2
+ from tortoise import Tortoise, run_async
3
  from .Users.UserRoutes import user_router
4
+ from .modelInit import TORTOISE_ORM
 
 
 
5
 
6
  app = FastAPI()
7
 
8
 
9
  @app.on_event("startup")
10
  async def startup_event():
11
+ await Tortoise.init(config=TORTOISE_ORM)
12
+ await Tortoise.generate_schemas()
 
 
13
 
14
 
15
  @app.get("/")
 
18
 
19
 
20
  app.include_router(user_router)
21
+
 
 
22
 
23
 
24
  async def main():
App/discovery.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # discover_models.py
2
+
3
+ import importlib
4
+ import os
5
+ from typing import List
6
+
7
+ def discover_models(target_file: str,directory: str="./") -> List[str]:
8
+ """
9
+ Discover target file (e.g., models.py) in all directories and subdirectories.
10
+
11
+ :param directory: The root directory to start searching from.
12
+ :param target_file: The filename to look for in subdirectories (e.g., "models.py").
13
+ :return: A list of module paths as strings.
14
+ """
15
+ model_modules = []
16
+
17
+ # Traverse directory and subdirectories
18
+ for root, _, files in os.walk(directory):
19
+ if target_file in files:
20
+ # Construct the module path, converting file path to dot notation
21
+ relative_path = os.path.relpath(root, directory)
22
+ module_name = f"{directory.replace('/', '.')}.{relative_path.replace('/', '.')}.{target_file[:-3]}"
23
+ model_modules.append(module_name.replace('...',""))
24
+ print(model_modules)
25
+ return model_modules
App/modelInit.py CHANGED
@@ -1,6 +1,16 @@
1
- import databases
2
- import orm
3
- import psycopg2
 
 
 
 
 
 
 
 
 
 
4
 
5
  # HOST=aws.connect.psdb.cloud
6
  # USERNAME=kn9rzjlad1tw8bvojqg9
@@ -8,9 +18,9 @@ import psycopg2
8
  # DATABASE=movie-website
9
 
10
 
11
- database = databases.Database(
12
- "postgresql+asyncpg://postgres:[email protected]:5432/postgres"
13
- )
14
  # database = databases.Database(
15
  # "postgresql+asyncpg://user:password@db:5432/mydatabase"
16
  # )
@@ -22,4 +32,4 @@ database = databases.Database(
22
 
23
 
24
  # databases = databases.Database(**args)
25
- models = orm.ModelRegistry(database=database)
 
1
+ from App.discovery import discover_models
2
+
3
+ DATABASE_URL = "sqlite://db.sqlite3" # Example: SQLite for local development
4
+
5
+ TORTOISE_ORM = {
6
+ "connections": {"default": DATABASE_URL},
7
+ "apps": {
8
+ "models": {
9
+ "models": discover_models("Model.py"), # Automatically discover models in the "models" directory
10
+ "default_connection": "default",
11
+ }
12
+ },
13
+ }
14
 
15
  # HOST=aws.connect.psdb.cloud
16
  # USERNAME=kn9rzjlad1tw8bvojqg9
 
18
  # DATABASE=movie-website
19
 
20
 
21
+ # database = databases.Database(
22
+ # "postgresql+asyncpg://postgres:[email protected]:5432/postgres"
23
+ # )
24
  # database = databases.Database(
25
  # "postgresql+asyncpg://user:password@db:5432/mydatabase"
26
  # )
 
32
 
33
 
34
  # databases = databases.Database(**args)
35
+
requirements.txt CHANGED
@@ -1,20 +1,8 @@
1
- asyncpg==0.27.0
2
- click==8.1.3
3
- databases==0.7.0
4
- fastapi==0.92.0
5
- Flask==2.2.2
6
- greenlet==2.0.2
7
- itsdangerous==2.1.2
8
- orm==0.3.1
9
- psycopg2-binary==2.9.5
10
- SQLAlchemy==1.4.46
11
- starlette==0.25.0
12
- typesystem==0.3.1
13
- Werkzeug==2.2.2
14
- passlib # for password hashing
15
  pydantic[email]
16
- uvicorn==0.21.1
17
- gunicorn
18
- orm[mysql]
19
- python-multipart
20
- asyncmy
 
1
+ fastapi
2
+ passlib
 
 
 
 
 
 
 
 
 
 
 
 
3
  pydantic[email]
4
+ uvicorn
5
+ jose
6
+ httpx
7
+ pytest
8
+ tortoise-orm