new age with tortole user works but not tests
Browse files- .gitignore +2 -0
- .gitpod.yml +10 -0
- App/Analytics/{Model.py → Model__.py} +0 -0
- App/CommentLikes/CommentLikesRoutes.py +0 -12
- App/CommentLikes/Model.py +0 -19
- App/CommentLikes/Schemas.py +0 -14
- App/Comments/CommentRoutes.py +0 -51
- App/Comments/Model.py +0 -21
- App/Comments/Schemas.py +0 -26
- App/Comments/__pycache__/CommentRoutes.cpython-38.pyc +0 -0
- App/Comments/__pycache__/Model.cpython-38.pyc +0 -0
- App/Comments/__pycache__/Schemas.cpython-38.pyc +0 -0
- App/Post/Model.py +0 -19
- App/Post/PostRoutes.py +0 -30
- App/Post/Schemas.py +0 -19
- App/Post/__pycache__/Model.cpython-38.pyc +0 -0
- App/Post/__pycache__/PostRoutes.cpython-38.pyc +0 -0
- App/Post/__pycache__/Schemas.cpython-38.pyc +0 -0
- App/PostLikes/Model.py +0 -19
- App/PostLikes/PostLikesRoutes.py +0 -43
- App/PostLikes/Schemas.py +0 -14
- App/PostLikes/__pycache__/Model.cpython-38.pyc +0 -0
- App/PostLikes/__pycache__/PostLikesRoutes.cpython-38.pyc +0 -0
- App/PostLikes/__pycache__/Schemas.cpython-38.pyc +0 -0
- App/Tests/test_user_routes.py +58 -0
- App/Users/Model.py +71 -23
- App/Users/Schemas.py +92 -8
- App/Users/UserRoutes.py +100 -23
- App/app.py +5 -11
- App/discovery.py +25 -0
- App/modelInit.py +17 -7
- requirements.txt +7 -19
.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
|
3 |
-
import
|
4 |
-
import
|
5 |
-
import pydantic
|
6 |
from passlib.context import CryptContext
|
7 |
-
|
8 |
-
|
|
|
9 |
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
"
|
18 |
-
|
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
|
2 |
-
from
|
|
|
3 |
from passlib.context import CryptContext
|
4 |
|
5 |
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
6 |
|
7 |
|
8 |
-
|
9 |
-
|
10 |
-
name: str
|
11 |
-
|
12 |
-
|
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
|
|
|
|
|
|
|
3 |
from .Model import User
|
4 |
-
from
|
5 |
-
from
|
|
|
|
|
6 |
|
7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
user_router = APIRouter(tags=["User"])
|
9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
|
11 |
-
|
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 .
|
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
|
14 |
-
|
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 |
-
|
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
|
2 |
-
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
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 |
-
|
|
|
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 |
-
|
2 |
-
|
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
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
|
|
1 |
+
fastapi
|
2 |
+
passlib
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3 |
pydantic[email]
|
4 |
+
uvicorn
|
5 |
+
jose
|
6 |
+
httpx
|
7 |
+
pytest
|
8 |
+
tortoise-orm
|