From 9336b1f213746bdfd2aae9f4e25ee88e5b04340f Mon Sep 17 00:00:00 2001 From: jamido1 <66392050+jamido1@users.noreply.github.com> Date: Sun, 6 Feb 2022 16:26:18 +0100 Subject: [PATCH 1/4] contact us security --- bigfastapi/contact_secure.py | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 bigfastapi/contact_secure.py diff --git a/bigfastapi/contact_secure.py b/bigfastapi/contact_secure.py new file mode 100644 index 0000000..0a4d965 --- /dev/null +++ b/bigfastapi/contact_secure.py @@ -0,0 +1,46 @@ +from uuid import uuid4 +import fastapi as fastapi +import passlib.hash as _hash +from bigfastapi.models import contact_secure_model as model +from .utils import utils +from fastapi import APIRouter, HTTPException, status +import sqlalchemy.orm as orm +from bigfastapi.db.database import get_db +from .schemas import contact_secure_schemas as schemas + +app = APIRouter(tags=["contact us security"]) + + +@app.post("/contact/secure", status_code=201) +def create_pin(secure: schemas.ContactSecureCreate, db: orm.Session = fastapi.Depends(get_db)): + c_secure = db.query(model.ContactSecure).filter(model.ContactSecure.email == secure.email).first() + print(c_secure) + if c_secure is not None: + raise fastapi.HTTPException(status_code=403, detail="Email already exist") + + cont = model.ContactSecure(id=uuid4().hex, + email=secure.email, + code=secure.code) + db.add(cont) + db.commit() + db.refresh(cont) + return {"message": f"pin created for {cont.email}"} + + +@app.post("/contact/secure/login", status_code=200) +async def login(secure: schemas.ContactSecureLogin, db: orm.Session = fastapi.Depends(get_db)): + secure_info = db.query(model.ContactSecure).filter(model.ContactSecure.email == secure.email).first() + if secure_info.code == secure.code: + return {"message": "Welcome back"} + raise fastapi.HTTPException(status_code=403, detail="pin Incorrect") + + +@app.put("/contact/secure/{email}") +def ForgotSecurePin(secure: schemas.CodeUpdate, email: str, db: orm.Session = fastapi.Depends(get_db)): + secure_code = db.query(model.ContactSecure).filter(model.ContactSecure.email == email).first() + if secure_code: + secure_code.code = secure.code + db.commit() + db.refresh(secure_code) + return {"message": "Code changed, kindly log in"} + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Email not registered") From 949f01dcd707e16a3dff524ffbca96a11ecd426d Mon Sep 17 00:00:00 2001 From: jamido1 <66392050+jamido1@users.noreply.github.com> Date: Sun, 6 Feb 2022 16:29:39 +0100 Subject: [PATCH 2/4] contact us security --- bigfastapi/models/contact_secure_model.py | 17 +++ bigfastapi/schemas/contact_secure_schemas.py | 29 +++++ bigfastapi/video.py | 121 +++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 bigfastapi/models/contact_secure_model.py create mode 100644 bigfastapi/schemas/contact_secure_schemas.py create mode 100644 bigfastapi/video.py diff --git a/bigfastapi/models/contact_secure_model.py b/bigfastapi/models/contact_secure_model.py new file mode 100644 index 0000000..0a924d6 --- /dev/null +++ b/bigfastapi/models/contact_secure_model.py @@ -0,0 +1,17 @@ +import passlib.hash as _hash +import datetime as dt +from sqlalchemy.schema import Column +from sqlalchemy.types import String, DateTime +from uuid import uuid4 +import bigfastapi.db.database as database + + +class ContactSecure(database.Base): + __tablename__ = "contact secure" + id = Column(String(255), primary_key=True, index=True, default=uuid4().hex) + email = Column(String(255), index=True) + code = Column(String(255), nullable=False) + date_created = Column(DateTime, default=dt.datetime.utcnow) + + def verify_code(self, code: str): + return (code, self.code) diff --git a/bigfastapi/schemas/contact_secure_schemas.py b/bigfastapi/schemas/contact_secure_schemas.py new file mode 100644 index 0000000..5ea9a63 --- /dev/null +++ b/bigfastapi/schemas/contact_secure_schemas.py @@ -0,0 +1,29 @@ +import datetime as dt +import pydantic + + +class SecureBase(pydantic.BaseModel): + email: str + + +class ContactSecureCreate(SecureBase): + code: str + + class Config: + orm_mode = True + + +class CodeUpdate(pydantic.BaseModel): + code: str + + +class ContactSecureLogin(SecureBase): + code: str + + +class ContactSecure(SecureBase): + id: str + date_created: dt.datetime + + class Config: + orm_mode = True diff --git a/bigfastapi/video.py b/bigfastapi/video.py new file mode 100644 index 0000000..a76c816 --- /dev/null +++ b/bigfastapi/video.py @@ -0,0 +1,121 @@ +from __future__ import unicode_literals +import requests +import youtube_dl +from uuid import uuid4 +from bs4 import BeautifulSoup +from fastapi import APIRouter, status, HTTPException, BackgroundTasks +from isodate import parse_duration +from typing import List +import fastapi +import sqlalchemy.orm as orm +from .auth_api import is_authenticated +from .schemas import users_schemas +from .schemas import video_schemas as schema +from .models import video_model as model + +from bigfastapi.db.database import get_db + +app = APIRouter(tags=["video streaming and download"]) + + +@app.post("/video/stream") +def stream_videos(video: schema.videoSearch, + db: orm.Session = fastapi.Depends(get_db), + user: users_schemas.User = fastapi.Depends(is_authenticated) + ): + videos = get_video(url=video.url) + vid = model.videos(id=uuid4().hex, title=videos["title"], url=videos["track_url"], + thumbnail=videos["thumbnail_url"], duration=videos["duration"], added_by=user.id) + db.add(vid) + db.commit() + db.refresh(vid) + return {"message": "video saved successfully", "video": schema.videos.from_orm(vid)} + + +@app.get("/video/stream", response_model=List[schema.videos]) +def user_stream_list(db: orm.Session = fastapi.Depends(get_db), + user: users_schemas.User = fastapi.Depends(is_authenticated) + ): + videos = db.query(model.videos).filter(model.videos.added_by == user.id).all() + return list(map(schema.videos.from_orm, videos)) + + +@app.get("/video/stream/{video_id}") +def get_stream(video_id: str, db: orm.Session = fastapi.Depends(get_db), + user: users_schemas.User = fastapi.Depends(is_authenticated) + ): + videos = db.query(model.videos).filter(model.videos.id == video_id).first() + if videos: + return {"message": "successful", "video": schema.videos.from_orm(videos)} + return {"message": "not found"} + + +@app.put("/video/stream/{video_id}/likes") +def like_videos(action: str, video_id: str, db: orm.Session = fastapi.Depends(get_db), + user: users_schemas.User = fastapi.Depends(is_authenticated) + ): + if action not in ["like", "unlike"]: + return {"status": False, "message": {f"{action} not supported. Consider using 'like' or 'unlike'."}} + video = db.query(model.videos).filter(model.videos.id == video_id).first() + if not video: + return {"message": "video not found"} + if action == "like": + video.like() + elif action == "unlike": + video.unlike(), + + db.commit() + db.refresh(video) + return {"status": True, "data": video} + + +@app.delete("/video/stream/{video_id}") +def delete_video(video_id: str, db: orm.Session = fastapi.Depends(get_db), + user: users_schemas.User = fastapi.Depends(is_authenticated) + ): + video = model.video_picker(user=user, id=video_id, db=db) + if video: + db.delete(video) + db.commit() + return video + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="video never existed") + + +@app.post("/video/download") +def download_videos(video_id: str, background_tasks: BackgroundTasks, + user: users_schemas.User = fastapi.Depends(is_authenticated), + db: orm.Session = fastapi.Depends(get_db)): + video = db.query(model.videos).filter(model.videos.id == video_id).first() + if not video: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, detail="video doesn't exist") + background_tasks.add_task(download, url=video.url) + return {"message": "downloading in background............."} + + +# **************************************video services****************************************# + + +def get_video(url): + res = requests.get(url) + content = res.content + soup = BeautifulSoup(content, "html.parser") + + return { + "title": soup.select_one('meta[itemprop="name"][content]')["content"], + "track_url": soup.select_one('link[itemprop="url"]')["href"], + "thumbnail_url": soup.select_one('link[itemprop="thumbnailUrl"]')["href"], + "duration": str( + parse_duration( + soup.select_one('meta[itemprop="duration"][content]')["content"] + ) + ), + } + + +def download(url): + ydl_opts = {'outtmpl': 'C:/Users/D E L E/CODE/bigfastapi/download/%(title)s.%(ext)s'} + with youtube_dl.YoutubeDL(ydl_opts) as ydl: + load = ydl.download([url]) + return {"message": "downloaded successfully", "video": load} From 42a5e9903be81ed689ee50ca3bfb477d095c6b6a Mon Sep 17 00:00:00 2001 From: jamido1 <66392050+jamido1@users.noreply.github.com> Date: Sun, 6 Feb 2022 16:34:17 +0100 Subject: [PATCH 3/4] video streaming and download for bigfastapi --- bigfastapi/models/video_model.py | 37 +++++++++++++++++++++++++++++ bigfastapi/schemas/video_schemas.py | 31 ++++++++++++++++++++++++ bigfastapi/video.py | 2 +- main.py | 8 ++++--- requirements.txt | 3 +++ 5 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 bigfastapi/models/video_model.py create mode 100644 bigfastapi/schemas/video_schemas.py diff --git a/bigfastapi/models/video_model.py b/bigfastapi/models/video_model.py new file mode 100644 index 0000000..569f40d --- /dev/null +++ b/bigfastapi/models/video_model.py @@ -0,0 +1,37 @@ +import sqlalchemy.orm as orm +from sqlalchemy.schema import Column +from sqlalchemy.types import String, Integer, DateTime +from sqlalchemy.ext.hybrid import hybrid_method +from sqlalchemy import ForeignKey +from sqlalchemy.sql import func +from uuid import uuid4 +from bigfastapi.db import database +import bigfastapi.schemas.users_schemas as schema + + +class videos(database.Base): + __tablename__ = "videos" + id = Column(String(255), primary_key=True, index=True, default=uuid4().hex) + title = Column(String(255), index=True) + url = Column(String(255), index=True) + thumbnail = Column(String(255), index=True) + duration = Column(String(255), index=True) + added_by = Column(String(255), ForeignKey("users.id")) + likes = Column(Integer, default=0, nullable=False) + time_added = Column(DateTime(timezone=True), server_default=func.now()) + + @hybrid_method + def like(self): + self.likes += 1 + return self.likes + + @hybrid_method + def unlike(self): + self.likes -= 1 + return self.likes + + +def video_picker(user: schema.User, + id: str, db: orm.Session): + video = db.query(videos).filter_by(added_by=user.id).filter(videos.id == id).first() + return video diff --git a/bigfastapi/schemas/video_schemas.py b/bigfastapi/schemas/video_schemas.py new file mode 100644 index 0000000..abf6c89 --- /dev/null +++ b/bigfastapi/schemas/video_schemas.py @@ -0,0 +1,31 @@ +import datetime as dt +from typing import Optional +from pydantic import BaseModel, HttpUrl + + +class videoSearch(BaseModel): + url: HttpUrl + + +class videoBase(BaseModel): + id: str + title: str + url: str + thumbnail: str + duration: str + added_by: str + time_added: dt.datetime + + class Config: + orm_mode = True + validate_all = True + validate_assignment = True + + +class videos(videoBase): + likes = int + + class Config: + orm_mode = True + validate_all = True + validate_assignment = True diff --git a/bigfastapi/video.py b/bigfastapi/video.py index a76c816..acb7eb3 100644 --- a/bigfastapi/video.py +++ b/bigfastapi/video.py @@ -94,7 +94,7 @@ def download_videos(video_id: str, background_tasks: BackgroundTasks, return {"message": "downloading in background............."} -# **************************************video services****************************************# +# **************************************video services**************************************************# def get_video(url): diff --git a/main.py b/main.py index 9a9e300..d1d15e4 100644 --- a/main.py +++ b/main.py @@ -12,10 +12,8 @@ # Import all the functionality that BFA provides -from bigfastapi.faq import app as faq from bigfastapi.contact import app as contact -from bigfastapi.blog import app as blog -from bigfastapi.pages import app as pages +from bigfastapi.contact_secure import app as contact_secure from bigfastapi.files import app as files from bigfastapi.users import app as accounts from bigfastapi.comments import app as comments @@ -30,6 +28,7 @@ from fastapi.testclient import TestClient from bigfastapi.banks import router as banks from bigfastapi.email import app as email +from bigfastapi.video import app as video from bigfastapi.organization import app as organization from bigfastapi.qrcode import app as qrcode from bigfastapi.settings import app as settings @@ -40,6 +39,7 @@ from bigfastapi.sms import app as sms + # Create the application app = FastAPI() @@ -63,6 +63,8 @@ app.include_router(countries, tags=["Countries"]) app.include_router(faq) app.include_router(contact) +app.include_router(video) +app.include_router(contact_secure) app.include_router(blog, tags=["Blog"]) app.include_router(pages, tags=["Pages"]) app.include_router(plans, tags=['Plans']) diff --git a/requirements.txt b/requirements.txt index bb54c6a..f5e5eb0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ alembic asgiref async-timeout blinker +bs4 certifi cffi charset-normalizer @@ -19,6 +20,7 @@ fastapi-utils greenlet h11 idna +isodate Jinja2 MarkupSafe packaging @@ -47,5 +49,6 @@ uvicorn watchgod websockets validators +youtube-dl pytest fastapi-pagination \ No newline at end of file From 88a81d04e7ac9cfb96cbc9a005232aee32526352 Mon Sep 17 00:00:00 2001 From: jamido1 <66392050+jamido1@users.noreply.github.com> Date: Sun, 6 Feb 2022 16:37:38 +0100 Subject: [PATCH 4/4] video streaming and download for bigfastapi --- tests/test_videos.py | 82 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 tests/test_videos.py diff --git a/tests/test_videos.py b/tests/test_videos.py new file mode 100644 index 0000000..f110ede --- /dev/null +++ b/tests/test_videos.py @@ -0,0 +1,82 @@ +from fastapi.testclient import TestClient +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from bigfastapi.db.database import get_db, Base +from bigfastapi.auth import is_authenticated +from bigfastapi.schemas.users_schemas import User +from main import app +from uuid import uuid4 + +SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" + +engine = create_engine( + SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} +) +TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +Base.metadata.create_all(bind=engine) + + +def override_get_db(): + try: + db = TestingSessionLocal() + yield db + finally: + db.close() + + +client = TestClient(app) + +app.dependency_overrides[get_db] = override_get_db + + +async def override_is_authenticated(): + user_data = { + "id": uuid4().hex, + "email": "test@zuri.com", + "first_name": "Sparrow", + "last_name": "Jack", + "phone_number": "+2345669506045", + "password": "hashedpassword", + "is_active": True, + "is_verified": True, + "is_superuser": True, + "organization": "Dorime" + } + return User(**user_data) + + +app.dependency_overrides[is_authenticated] = override_is_authenticated + + +def test_stream_videos(): + response = client.post( + '/video/stream', + json={"url": "https://www.youtube.com/watch?v=T5y9kRXgYEM"}) + assert response.status_code == 200, response.text + data = response.json() + print(data) + + +def test_user_stream_list(): + response = client.get('/video/stream') + assert response.status_code == 200, response.text + + +def test_get_stream(): + response = client.get('/video/stream/f4335345f43564f454436') + assert response.status_code == 200, response.text + + +def test_delete_video(): + response = client.delete('/video/stream/f4335345f43564f454436') + assert response.status_code == 404, response.text + + +def test_download_videos(): + response = client.post( + '/video/download', + json={"video_id": "f4335345f43564f454436"}) + assert response.status_code == 422, response.text + data = response.json() + print(data)