diff --git a/README.md b/README.md index f2acf67..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,63 +0,0 @@ -# User authentication using FastAPI (a python frameworks), MongoDB (for database), Docker Compose (for deployment) -## How to start the application -**Watch the video:** (below the video are the commands on how to start the application) -* https://www.youtube.com/watch?v=Bw5rQXgvHYc - -**The commands:** - -First you have to git clone the files by entering in your terminal: -``` -$ git clone https://github.com/AtamanKit/fastapi_users_docker.git -``` -Then start the application: -``` -$ docker-compose up -d -``` -The above command will both create the images and start the containers (2 images and 2 containers - one for the FastAPI application and one for the MongoDB database). - -For visualizing the application, open up your browser and enter: - -* http://127.0.0.1/docs - -In the application we have seven sections: -* For authentication (the right green "Authorize" button from the above); -* For creating users (3 roles are acceptable only: "admin", "dev", "simple mortal", you'll see an error if not respecting the rule); -* For creating tokens by entering user's credentials; -* For listing the users; -* For watching the current user (only if authenticated); -* For modifying user properties (only if authenticated with admin role); -* For deleting the user. - -To see the runing containers in docker, enter in the terminal: -``` -$ docker ps -``` -To see the database and collection created (database name is: myTestDB, collection: users) enter in your terminal: -``` -$ docker exec -it bash -``` - -## Configuration and file structure -Our file structure is: -``` -. -├── app -│   ├── Dockerfile -│   ├── __init__.py -│   ├── main.py -│   ├── requirements.txt -│   └── src -│   ├── __init__.py -│   ├── dependecies.py -│   ├── models.py -│   ├── routers.py -│   └── settings.py -└── docker-compose.yml -``` -In the app directory in ```main.py``` file we make all the dependencies and routers importing from the same name files located in ```src``` directory. - -```src``` directory is the one that containes all the needed pydantic models (models.py), database and authentication variables (settings.py). - -Authentication is made by using ```bearer``` scheme with ```token``` creation and usage. - -```dependecies.py``` is the file containing authentication fucntions (I also made an authentication middleware located in ```main.py``` file in the root directory using ```basic``` scheme, this function serves as an example purpose only). diff --git a/app/main.py b/app/main.py index 8db8967..7441232 100644 --- a/app/main.py +++ b/app/main.py @@ -7,16 +7,31 @@ from fastapi import ( ) from src.dependecies import authenticate_user -from src.routers import router +from src.routers.routers import router +from src.routers.post import post +from src.routers.history_find import history import base64 import binascii +from fastapi.middleware.cors import CORSMiddleware #------------------ FastAPI variable ---------------------------------- app = FastAPI() + +origins = ["*"] + +app.add_middleware( + CORSMiddleware, + allow_origins=origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + # ================ Authentication Middleware ======================= #----------- Here authentication is based on basic scheme, #----------- another authentication, based on bearer scheme, is used throughout @@ -43,4 +58,6 @@ async def authenticate(request: Request, call_next): return response # ================= Routers inclusion from src directory =============== -app.include_router(router) \ No newline at end of file +app.include_router(post) +app.include_router(router) +app.include_router(history) \ No newline at end of file diff --git a/app/requirements.txt b/app/requirements.txt index a260442..41ec878 100644 --- a/app/requirements.txt +++ b/app/requirements.txt @@ -23,3 +23,5 @@ six==1.16.0 starlette==0.14.2 typing-extensions==3.10.0.0 uvicorn==0.14.0 +python-multipart==0.0.5 +aiofiles==0.8.0 \ No newline at end of file diff --git a/app/src/dependecies.py b/app/src/dependecies.py index b34beda..0d300b6 100644 --- a/app/src/dependecies.py +++ b/app/src/dependecies.py @@ -17,7 +17,7 @@ def verify_password(plain_password, hashed_password): async def get_user(id: str): - if (user := await db["users"].find_one({"_id": id})) is not None: + if (user := await db["users"].find_one({"username": id})) is not None: return user diff --git a/app/src/models/history_find.py b/app/src/models/history_find.py new file mode 100644 index 0000000..63cd1ee --- /dev/null +++ b/app/src/models/history_find.py @@ -0,0 +1,91 @@ +import json +from bson import ObjectId +from pydantic import BaseModel, Field +from typing import List, Optional, Union + + +class PyObjectId(ObjectId): + @classmethod + def __get_validators__(cls): + yield cls.validate + + @classmethod + def validate(cls, v): + if not ObjectId.is_valid(v): + raise ValueError("Invalid objectid") + return ObjectId(v) + + @classmethod + def __modify_schema__(cls, field_schema): + field_schema.update(type="string") + +class HistoryFindModel(BaseModel): + id: PyObjectId = Field(default_factory=PyObjectId, alias="_id") + full_name: str + username: Union[str, None] = None + age: int + sick : List[str] + created_at: Optional[str] = None + post_id : str + key_find : str + + class Config: + orm_mode = True + case_sensitive = True + allow_population_by_field_name = True + arbitrary_types_allowed = True + json_encoders = {ObjectId: str} + schema_extra = { + "example": { + "full_name": "John", + "username":"", + "age": 20, + "sick": ["simple mortal"], + "post_id" : "", + "key_find": "" + + } + } + + +# class UpdateHistoryFindModel(BaseModel): +# full_name: str +# age: int +# sick : List[str] +# created_at: Optional[str] = None +# post_id : str +# key_find : str + +# class Config: +# arbitrary_types_allowed = True +# json_encoders = {ObjectId: str} +# schema_extra = { +# "example": { +# "full_name": "John", +# "age": 20, +# "sick": "simple mortal", +# "key_find": "datetime" +# } +# } + + +# class ShowPostModel(BaseModel): +# id: PyObjectId = Field(default_factory=PyObjectId, alias="_id") +# full_name: str +# age: int +# sick : List[str] +# created_at: Optional[str] = None +# post_id : str +# key_find : str + +# class Config: +# arbitrary_types_allowed = True +# json_encoders = {ObjectId: str} +# schema_extra = { +# "example": { +# "full_name": "John", +# "age": 20, +# "sick": "simple mortal", +# "key_find": "datetime" +# } +# } diff --git a/app/src/models.py b/app/src/models/models.py similarity index 91% rename from app/src/models.py rename to app/src/models/models.py index 3fbd67d..cc60810 100644 --- a/app/src/models.py +++ b/app/src/models/models.py @@ -1,6 +1,6 @@ from bson import ObjectId from pydantic import BaseModel, Field -from typing import Optional +from typing import Optional, Union class PyObjectId(ObjectId): @@ -21,8 +21,10 @@ class PyObjectId(ObjectId): class UserModel(BaseModel): id: PyObjectId = Field(default_factory=PyObjectId, alias="_id") + username: Union[str, None] = None first_name: str last_name: str + avatar: str role : str is_active : str created_at: Optional[str] = None @@ -35,8 +37,10 @@ class UserModel(BaseModel): json_encoders = {ObjectId: str} schema_extra = { "example": { + "username": "", "first_name": "John", "last_name": "Doe", + "avatar":"", "role": "simple mortal", "is_active": "false", "created_at": "datetime", @@ -49,6 +53,7 @@ class UserModel(BaseModel): class UpdateUserModel(BaseModel): first_name: Optional[str] last_name: Optional[str] + avatar: str role: Optional[str] is_active: Optional[str] created_at: Optional[str] @@ -61,6 +66,7 @@ class UpdateUserModel(BaseModel): "example": { "first_name": "John", "last_name": "Doe", + "avatar":"", "role": "simple mortal", "is_active": "false", "created_at": "datetime", @@ -73,6 +79,7 @@ class ShowUserModel(BaseModel): id: PyObjectId = Field(default_factory=PyObjectId, alias="_id") first_name: Optional[str] last_name: Optional[str] + avatar: str role: Optional[str] is_active: Optional[str] created_at: Optional[str] @@ -85,6 +92,7 @@ class ShowUserModel(BaseModel): "example": { "first_name": "John", "last_name": "Doe", + "avatar":"", "role": "simple mortal", "created_at": "datetime", "last_login": "datetime", diff --git a/app/src/models/post.py b/app/src/models/post.py new file mode 100644 index 0000000..bb8130a --- /dev/null +++ b/app/src/models/post.py @@ -0,0 +1,171 @@ +import json +from bson import ObjectId +from pydantic import BaseModel, Field +from typing import List, Optional, Union + + +class PyObjectId(ObjectId): + @classmethod + def __get_validators__(cls): + yield cls.validate + + @classmethod + def validate(cls, v): + if not ObjectId.is_valid(v): + raise ValueError("Invalid objectid") + return ObjectId(v) + + @classmethod + def __modify_schema__(cls, field_schema): + field_schema.update(type="string") + +class DataPost(BaseModel): + name: str + level: str + content: List[str] +class DataSmallPost(BaseModel): + name: str + level: int + point: int +class Point(BaseModel): + less10 : Optional[int] = 0 + form10to20: Optional[int] = 0 + form20to30: Optional[int] = 0 + form30to40: Optional[int] = 0 + form40to50: Optional[int] = 0 + form50to60: Optional[int] = 0 + bigger60: Optional[int] = 0 + total: Optional[int] = 0 +class PostModel(BaseModel): + id: PyObjectId = Field(default_factory=PyObjectId, alias="_id") + original_post: Union[str, None] = None + translation_post: Union[str, None] = None + link : Union[str, None] = None + is_active : str + created_at: Optional[str] = None + specialist: str + summary: str + data : List[DataPost] + point : Point + class Config: + orm_mode = True + case_sensitive = True + allow_population_by_field_name = True + arbitrary_types_allowed = True + json_encoders = {ObjectId: str} + schema_extra = { + "example": { + "original_post": "Joh111", + "translation_post": "Doe11111", + "link": "simple mortal111", + "is_active": "false", + "created_at": "07/20/22 02: 26: 54", + "specialist": "", + "summary": "", + "data": [ + { + "name": "abc1", + "level": "user", + "content": [ + "Hoàng Anh Hoàng Anh Hoàng Anh Hoàng AnhHoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh", + "Hoàng Anh Hoàng Anh Hoàng Anh Hoàng AnhHoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh", + "Hoàng Anh Hoàng Anh Hoàng Anh Hoàng AnhHoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh", + "Hoàng Anh Hoàng Anh Hoàng Anh Hoàng AnhHoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh" + ] + }, + { + "name": "abc2", + "level": "user", + "content": [ + "Hoàng Anh Hoàng Anh Hoàng Anh Hoàng AnhHoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh", + "Hoàng Anh Hoàng Anh Hoàng Anh Hoàng AnhHoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh", + "Hoàng Anh Hoàng Anh Hoàng Anh Hoàng AnhHoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh", + "Hoàng Anh Hoàng Anh Hoàng Anh Hoàng AnhHoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh" + ] + }, + { + "name": "abc", + "level": "user", + "content": [ + "Hoàng Anh Hoàng Anh Hoàng Anh Hoàng AnhHoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh", + "Hoàng Anh Hoàng Anh Hoàng Anh Hoàng AnhHoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh", + "Hoàng Anh Hoàng Anh Hoàng Anh Hoàng AnhHoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh", + "Hoàng Anh Hoàng Anh Hoàng Anh Hoàng AnhHoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh" + ] + }, + { + "name": "abc3", + "level": "user", + "content": [ + "Hoàng Anh Hoàng Anh Hoàng Anh Hoàng AnhHoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh", + "Hoàng Anh Hoàng Anh Hoàng Anh Hoàng AnhHoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh", + "Hoàng Anh Hoàng Anh Hoàng Anh Hoàng AnhHoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh", + "Hoàng Anh Hoàng Anh Hoàng Anh Hoàng AnhHoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh Hoàng Anh" + ] + } + ], + "point": { + "less10": 0, + "form10to20": 0, + "form20to30": 0, + "form30to40": 0, + "form40to50": 0, + "form50to60": 0, + "bigger60": 0, + "total": 0 + } + } + } + + +class UpdatePostModel(BaseModel): + original_post: Union[str, None] = None + translation_post: Union[str, None] = None + link : Union[str, None] = None + is_active : str + created_at: Optional[str] = None + specialist: str + summary: str + data : List[DataPost] + point : Point + class Config: + arbitrary_types_allowed = True + json_encoders = {ObjectId: str} + schema_extra = { + "example": { + "original_post": "John", + "translation_post": "Doe", + "link": "simple mortal", + "is_active": "false", + "created_at": "datetime", + "specialist": "", + "summary": "", + "data": "" + } + } + + +class ShowPostModel(BaseModel): + id: PyObjectId = Field(default_factory=PyObjectId, alias="_id") + original_post: Optional[str] + translation_post: Optional[str] + link: Optional[str] + is_active: Optional[str] + specialist: Optional[str] + summary: Optional[str] + data : List[DataSmallPost] + point : Point + class Config: + arbitrary_types_allowed = True + json_encoders = {ObjectId: str} + schema_extra = { + "example": { + "original_post": "John", + "translation_post": "Doe", + "link": "simple mortal", + "is_active": "false", + "created_at": "datetime", + "specialist": "", + "summary": "", + } + } diff --git a/app/src/routers/history_find.py b/app/src/routers/history_find.py new file mode 100644 index 0000000..4192122 --- /dev/null +++ b/app/src/routers/history_find.py @@ -0,0 +1,57 @@ +from fastapi import ( + APIRouter, + Depends, + status, + HTTPException +) +from fastapi.responses import JSONResponse +from fastapi.encoders import jsonable_encoder +from fastapi.security import OAuth2PasswordRequestForm +from src.settings import db, ACCESS_TOKEN_EXPIRE_MINUTES +from ..models.history_find import * +from typing import List +from datetime import datetime, timedelta + +from ..models.models import ( + UserModel, + ShowUserModel, + UpdateUserModel +) +from ..dependecies import ( + get_current_user, + authenticate_user, + create_access_token, + get_password_hash +) +import re + +history = APIRouter() +##############################POST############################################### +@history.post("/create_history", response_description="Add new user", response_model=HistoryFindModel) +async def create_history(history: HistoryFindModel, current_user: ShowUserModel = Depends(get_current_user)): + datetime_now = datetime.now() + post.created_at = datetime_now.strftime("%m/%d/%y %H:%M:%S") + post = jsonable_encoder(post) + new_post = await db["history_find"].insert_one(post) + created = await db["history_find"].find_one({"_id": new_post.inserted_id}) + return JSONResponse(status_code=status.HTTP_201_CREATED, content=created) + +@history.get( + "/list_history", response_description="List all posts", response_model=List[HistoryFindModel] +) +async def list_post(current_user: ShowUserModel = Depends(get_current_user)): + history_find = await db["history_find"].find().to_list(1000) + return history_find + + +# @post.get( +# "/get_post_by_name", response_description="Get a single posot", response_model=PostModel +# ) +# async def show_post(id: str): +# print(id) +# post = await db["posts"].find_one({"_id": id}) +# print(post) +# if post is not None: +# return post +# else: +# return None \ No newline at end of file diff --git a/app/src/routers/post.py b/app/src/routers/post.py new file mode 100644 index 0000000..4099f22 --- /dev/null +++ b/app/src/routers/post.py @@ -0,0 +1,202 @@ +from email.policy import default +from fastapi import APIRouter,Depends,status,HTTPException,UploadFile, File, Header +from fastapi.responses import JSONResponse, FileResponse, StreamingResponse +from fastapi.encoders import jsonable_encoder +from fastapi.security import OAuth2PasswordRequestForm +import logging +from ..dependecies import ( + get_current_user, + authenticate_user, + create_access_token, + get_password_hash +) +from src.settings import db, ACCESS_TOKEN_EXPIRE_MINUTES +from ..models.post import ( + PostModel, + UpdatePostModel, + ShowPostModel +) +from ..models.models import ( + UserModel, + ShowUserModel, + UpdateUserModel +) +from ..dependecies import ( + get_current_user, + authenticate_user, + create_access_token, + get_password_hash +) +from ..models.history_find import * +from typing import List +from datetime import datetime +import os +import re +post = APIRouter() +##############################POST############################################### +@post.post("/create_post", response_description="Add new user", response_model=PostModel) +async def create_post(post: PostModel, current_user: UserModel = Depends(get_current_user)): + try: + datetime_now = datetime.now() + post.created_at = datetime_now.strftime("%m/%d/%y %H:%M:%S") + post = jsonable_encoder(post) + new_post = await db["posts"].insert_one(post) + created_post = await db["posts"].find_one({"_id": new_post.inserted_id}) + return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_post) + except NameError: + return NameError + +@post.get( + "/list_post", response_description="List all posts", response_model=List[ShowPostModel] +) +async def list_post(current_user: UserModel = Depends(get_current_user)): + posts = await db["posts"].find().to_list(1000) + print(posts) + print(len(posts)) + return posts + +@post.post( + "/find_list_post", response_description="search list posts", response_model=List[ShowPostModel] +) +async def list_post(history: HistoryFindModel, current_user: UserModel = Depends(get_current_user)): + + point_data=["point.less10", + "point.form10to20", + "point.form20to30", + "point.form30to40", + "point.form40to50", + "point.form50to60", + ] + age_sort = "point.total" + history = jsonable_encoder(history) + if history.get("age", None) != None: + if history.get("age") >59: + age_sort = "point.bigger60" + else: + age_sort = point_data[history.get("age")//10] + posts = await db["posts"].find({"translation_post": { "$regex": history.get("key_find") } }).sort(age_sort, -1).to_list(100) + + print(posts) + return posts + + +@post.post( + "/get_post_by_name", response_description="Get a single post", response_model=PostModel +) +async def get_post_by_name(history: HistoryFindModel, current_user: UserModel = Depends(get_current_user) + ): + datetime_now = datetime.now() + history.created_at = datetime_now.strftime("%m/%d/%y %H:%M:%S") + post = await db["posts"].find_one({"_id": history.post_id}) + history = jsonable_encoder(history) + new_his = await db["history_find"].insert_one(history) + created = await db["history_find"].find_one({"_id": new_his.inserted_id}) + print(created) + if post is not None: + return post + else: + return None + +@post.delete("/delete_post/{id}", response_description="Delete a post") +async def delete_user(id: str, current_user: UserModel = Depends(get_current_user)): + delete_result = await db["posts"].delete_one({"_id": id}) + if delete_result.deleted_count == 1: + return JSONResponse(status_code=status.HTTP_204_NO_CONTENT) + raise HTTPException(status_code=404, detail=f"Post {id} not found") + +@post.get("/score", response_description="score all post", response_model=List[ShowPostModel]) +async def score_all_post( current_user: UserModel = Depends(get_current_user)): + posts = await db["posts"].find().to_list(1000) + for dt_post in posts: + print(dt_post) + data_old = dt_post + dt_post["point"]["less10"] = await db["history_find"].count_documents({"post_id": dt_post["_id"], + "age":{ + '$gte': 0, '$lte': 9 + }}) + dt_post["point"]["form10to20"] = await db["history_find"].count_documents({"post_id": dt_post["_id"], + "age":{ + '$gte': 10, '$lte': 19 + }}) + dt_post["point"]["form20to30"] = await db["history_find"].count_documents({"post_id": dt_post["_id"], + "age":{ + '$gte': 20, '$lte': 29 + }}) + dt_post["point"]["form30to40"] = await db["history_find"].count_documents({"post_id": dt_post["_id"], + "age":{ + '$gte': 30, '$lte': 39 + }}) + dt_post["point"]["form40to50"] = await db["history_find"].count_documents({"post_id": dt_post["_id"], + "age":{ + '$gte': 40, '$lte': 49 + }}) + dt_post["point"]["form50to60"] = await db["history_find"].count_documents({"post_id": dt_post["_id"], + "age":{ + '$gte': 50, '$lte': 59 + }}) + dt_post["point"]["bigger60"] = await db["history_find"].count_documents({"post_id": dt_post["_id"], + "age":{ + '$gte': 60 + }}) + dt_post["point"]["total"] = await db["history_find"].count_documents({"post_id": dt_post["_id"]}) + # await db["posts"].update_one(data_old, dt_post) + await db["posts"].update_one({"_id": dt_post["_id"]}, {"$set": { + "point":{ + "less10": dt_post["point"]["less10"], + "form10to20": dt_post["point"]["form10to20"], + "form20to30": dt_post["point"]["form20to30"], + "form30to40": dt_post["point"]["form30to40"], + "form40to50": dt_post["point"]["form40to50"], + "form50to60": dt_post["point"]["form50to60"], + "bigger60": dt_post["point"]["bigger60"], + "total": dt_post["point"]["total"] + } + }}) + + return posts + + + +@post.post("/uploadfiles/") +async def create_upload_files( + files: List[UploadFile] = File(...), current_user: UserModel = Depends(get_current_user) +): + try: + file_name =[] + i = 0 + file_location = f"../media/" + for file in files: + now = datetime.now() + current_time = now.strftime("%H:%M:%S_%d-%m-%Y_") + file_save = file_location + current_time + str(i) + file.filename + file_name.append(current_time + str(i) + file.filename) + i = i + 1 + with open(file_save, "wb+") as file_object: + file_object.write(file.file.read()) + return {"filenames": file_name} + except Exception as e: + return JSONResponse( + status_code = status.HTTP_400_BAD_REQUEST, + content = { 'message' : str(e) } + ) + +@post.get("/showfile/{name}") +async def show_file(name: str, current_user: UserModel = Depends(get_current_user)): + file_path = f"../media/" + name + if os.path.exists(file_path): + return FileResponse(file_path) + return {"erro": "File not found!"} + +@post.get("/showvideo/{file_name}", response_class=FileResponse) +async def main(file_name: str, current_user: UserModel = Depends(get_current_user)): + file_path = f"../media/" + file_name + return file_path + +@post.get("/video") +async def video_endpoint(video_name, current_user: UserModel = Depends(get_current_user)): + def iterfile(): + video_path = f"../media/" + video_name + with open(video_path, mode="rb") as file_like: + yield from file_like + + return StreamingResponse(iterfile(), media_type="video/mp4") \ No newline at end of file diff --git a/app/src/routers.py b/app/src/routers/routers.py similarity index 62% rename from app/src/routers.py rename to app/src/routers/routers.py index f1de776..fa079c7 100644 --- a/app/src/routers.py +++ b/app/src/routers/routers.py @@ -7,35 +7,43 @@ from fastapi import ( from fastapi.responses import JSONResponse from fastapi.encoders import jsonable_encoder from fastapi.security import OAuth2PasswordRequestForm - -from .models import ( +from fastapi import File, UploadFile, FastAPI +from ..models.models import ( UserModel, ShowUserModel, UpdateUserModel ) -from .dependecies import ( +from ..dependecies import ( get_current_user, authenticate_user, create_access_token, get_password_hash ) -from .settings import db, ACCESS_TOKEN_EXPIRE_MINUTES +from ..settings import db, ACCESS_TOKEN_EXPIRE_MINUTES from typing import List from datetime import datetime, timedelta import re - +from pydantic import BaseModel, Field +class LoginRequest(BaseModel): + username: str + password: str router = APIRouter() - # ============= Creating path operations ============== -@router.post("/", response_description="Add new user", response_model=UserModel) -async def create_user(user: UserModel): +@router.post("/create_user", response_description="Add new user", response_model=UserModel) +async def create_user(user: UserModel, file: UploadFile = File(...)): if re.match("admin|dev|simple mortal", user.role): datetime_now = datetime.now() user.created_at = datetime_now.strftime("%m/%d/%y %H:%M:%S") user.password = get_password_hash(user.password) user = jsonable_encoder(user) + file_location = f"../media/" + current_time = datetime_now.strftime("%H:%M:%S_%d-%m-%Y_") + file_save = file_location + current_time + file.filename + user.avatar = current_time + file.filename + with open(file_save, "wb+") as file_object: + file_object.write(file.file.read()) new_user = await db["users"].insert_one(user) await db["users"].update_one({"_id": new_user.inserted_id}, { "$rename": {"password": "hashed_pass"}}) @@ -47,9 +55,11 @@ async def create_user(user: UserModel): -@router.post("/token") -async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()): - user = await authenticate_user(form_data.username, form_data.password) +@router.post("/login") +async def login_for_access_token(body: LoginRequest): + print(body) + user = await authenticate_user(body.username, body.password) + print(body) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -58,21 +68,41 @@ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends( ) access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( - data={"sub": user["_id"]}, expires_delta=access_token_expires + data={"sub": user["username"]}, expires_delta=access_token_expires ) - await db["users"].update_one({"_id": form_data.username}, {"$set": { + await db["users"].update_one({"username": body.username}, {"$set": { "last_login": datetime.now().strftime("%m/%d/%y %H:%M:%S"), "is_active": "true" }}) return {"access_token": access_token, "token_type": "bearer"} +@router.post("/token") +async def login_for_access_token_2(body: LoginRequest): + print(body) + user = await authenticate_user(body.username, body.password) + print(body) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorect ID or password", + headers={"WWW-Authenticate": "Bearer"}, + ) + access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + access_token = create_access_token( + data={"sub": user["username"]}, expires_delta=access_token_expires + ) + await db["users"].update_one({"username": body.username}, {"$set": { + "last_login": datetime.now().strftime("%m/%d/%y %H:%M:%S"), + "is_active": "true" + }}) + return {"access_token": access_token, "token_type": "bearer"} @router.get( "/list", response_description="List all users", response_model=List[ShowUserModel] ) -async def list_users(): +async def list_users(current_user: ShowUserModel = Depends(get_current_user)): users = await db["users"].find().to_list(1000) for user in users: user["is_active"] = "false" @@ -119,10 +149,9 @@ async def update_user(user_id: str, user: UpdateUserModel, current_user: UserMod -@router.delete("/{user_id}", response_description="Delete a user") -async def delete_user(user_id: str): +@router.delete("/delete_user/{user_id}", response_description="Delete a user") +async def delete_user(user_id: str, current_user: ShowUserModel = Depends(get_current_user)): delete_result = await db["users"].delete_one({"_id": user_id}) - if delete_result.deleted_count == 1: return JSONResponse(status_code=status.HTTP_204_NO_CONTENT) diff --git a/docker-compose.yml b/docker-compose.yml index d357fe2..5089d52 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,8 @@ services: - DB_URL=mongodb://db/myTestDB volumes: - ./app:/app + restart: always + db: image: mongo ports: