feat: convert to self-hosted version

This commit is contained in:
Jayden Pyles
2024-07-06 22:40:47 -05:00
parent 8808b493e6
commit 2bfc751d01
24 changed files with 1073 additions and 222 deletions
+1 -1
View File
@@ -183,5 +183,5 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
.pdm-python
.next
postgres_data
-49
View File
@@ -1,49 +0,0 @@
# STL
import os
import logging
from typing import Any
# PDM
import boto3
from mypy_boto3_dynamodb.service_resource import Table, DynamoDBServiceResource
LOG = logging.getLogger(__name__)
def connect_to_dynamo() -> Table:
region_name = os.getenv("AWS_REGION")
dynamodb: DynamoDBServiceResource = boto3.resource(
"dynamodb", region_name=region_name
)
return dynamodb.Table("scrape")
def insert(table: Table, item: dict[str, Any]) -> None:
i = table.put_item(Item=item)
LOG.info(f"Inserted item: {i}")
def query(table: Table, index_name: str, key_condition: Any) -> list[Any]:
try:
response = table.query(
IndexName=index_name, KeyConditionExpression=key_condition
)
items = response.get("Items", [])
for item in items:
LOG.info(f"Queried item: {item}")
return items
except Exception as e:
LOG.error(f"Failed to query table: {e}")
raise
def query_by_id(table: Table, key_condition: Any) -> list[Any]:
try:
response = table.query(KeyConditionExpression=key_condition)
items = response.get("Items", [])
for item in items:
LOG.info(f"Queried item: {item}")
return items
except Exception as e:
LOG.error(f"Failed to query table: {e}")
raise
+8 -10
View File
@@ -5,27 +5,28 @@ from io import StringIO
# PDM
import pandas as pd
from fastapi import FastAPI, HTTPException
from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from boto3.dynamodb.conditions import Key
# LOCAL
from api.backend.amazon import query, insert, query_by_id, connect_to_dynamo
from api.backend.job import query, insert
from api.backend.models import DownloadJob, SubmitScrapeJob, RetrieveScrapeJobs
from api.backend.scraping import scrape
from api.backend.auth.auth_router import auth_router
logging.basicConfig(
level=logging.INFO,
format="[%(levelname)s] %(asctime)s - %(name)s - %(message)s",
format="%(levelname)s: %(asctime)s - %(name)s - %(message)s",
handlers=[logging.StreamHandler()],
)
LOG = logging.getLogger(__name__)
app = FastAPI(title="api")
app.include_router(auth_router)
app.add_middleware(
CORSMiddleware,
@@ -54,10 +55,9 @@ async def submit_scrape_job(job: SubmitScrapeJob):
)
json_scraped = jsonable_encoder(scraped)
table = connect_to_dynamo()
job.result = json_scraped
job.id = uuid.uuid4().hex
insert(table, jsonable_encoder(job))
await insert(jsonable_encoder(job))
return JSONResponse(content=json_scraped)
except Exception as e:
return JSONResponse(content={"error": str(e)}, status_code=500)
@@ -67,8 +67,7 @@ async def submit_scrape_job(job: SubmitScrapeJob):
async def retrieve_scrape_jobs(retrieve: RetrieveScrapeJobs):
LOG.info(f"Retrieving jobs for account: {retrieve.user}")
try:
table = connect_to_dynamo()
results = query(table, "user", Key("user").eq(retrieve.user))
results = await query({"user": retrieve.user})
return JSONResponse(content=results)
except Exception as e:
LOG.error(f"Exception occurred: {e}")
@@ -79,8 +78,7 @@ async def retrieve_scrape_jobs(retrieve: RetrieveScrapeJobs):
async def download(download_job: DownloadJob):
LOG.info(f"Downloading job with id: {download_job.id}")
try:
table = connect_to_dynamo()
results = query_by_id(table, Key("id").eq(download_job.id))
results = await query({"id": download_job.id})
df = pd.DataFrame(results)
View File
+57
View File
@@ -0,0 +1,57 @@
# STL
from datetime import timedelta
# PDM
from fastapi import Depends, APIRouter, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
# LOCAL
from api.backend.schemas import User, Token, UserCreate
from api.backend.database import get_user_collection
from api.backend.auth.auth_utils import (
ACCESS_TOKEN_EXPIRE_MINUTES,
get_current_user,
authenticate_user,
get_password_hash,
create_access_token,
)
auth_router = APIRouter()
@auth_router.post("/api/auth/token", response_model=Token)
async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
user = await authenticate_user(form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
expire_minutes = (
int(ACCESS_TOKEN_EXPIRE_MINUTES) if ACCESS_TOKEN_EXPIRE_MINUTES else 60
)
access_token_expires = timedelta(minutes=expire_minutes)
access_token = create_access_token(
data={"sub": user.email}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@auth_router.post("/api/auth/signup", response_model=User)
async def create_user(user: UserCreate):
users_collection = get_user_collection()
hashed_password = get_password_hash(user.password)
user_dict = user.model_dump()
user_dict["hashed_password"] = hashed_password
del user_dict["password"]
_ = await users_collection.insert_one(user_dict)
return user_dict
@auth_router.get("/api/auth/users/me", response_model=User)
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
+101
View File
@@ -0,0 +1,101 @@
# STL
import os
from typing import Any, Optional
from datetime import datetime, timedelta
# PDM
from jose import JWTError, jwt
from dotenv import load_dotenv
from fastapi import Depends, HTTPException, status
from passlib.context import CryptContext
from fastapi.security import OAuth2PasswordBearer
# LOCAL
from api.backend.schemas import User, UserInDB, TokenData
from api.backend.database import get_user_collection
_ = load_dotenv()
SECRET_KEY = os.getenv("SECRET_KEY") or ""
ALGORITHM = os.getenv("ALGORITHM") or ""
ACCESS_TOKEN_EXPIRE_MINUTES = os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES")
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/token")
def verify_password(plain_password: str, hashed_password: str):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password: str):
return pwd_context.hash(password)
async def get_user(email: str):
user_collection = get_user_collection()
user = await user_collection.find_one({"email": email})
if not user:
return
return UserInDB(**user)
async def authenticate_user(email: str, password: str):
user = await get_user(email)
if not user:
return False
if not verify_password(password, user.hashed_password):
return False
return user
def create_access_token(
data: dict[str, Any], expires_delta: Optional[timedelta] = None
):
to_encode = data.copy()
if expires_delta:
expire = datetime.now() + expires_delta
else:
expire = datetime.now() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
async def get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload: Optional[dict[str, Any]] = jwt.decode(
token, SECRET_KEY, algorithms=[ALGORITHM]
)
if not payload:
raise credentials_exception
email = payload.get("sub")
if email is None:
raise credentials_exception
token_data = TokenData(email=email)
except JWTError:
raise credentials_exception
user = await get_user(email=token_data.email)
if user is None:
raise credentials_exception
return user
+23
View File
@@ -0,0 +1,23 @@
# STL
import os
from typing import Any
# PDM
from dotenv import load_dotenv
from motor.motor_asyncio import AsyncIOMotorClient
_ = load_dotenv()
MONGODB_URI = os.getenv("MONGODB_URI")
def get_user_collection():
client: AsyncIOMotorClient[dict[str, Any]] = AsyncIOMotorClient(MONGODB_URI)
db = client["scrape"]
return db["users"]
def get_job_collection():
client: AsyncIOMotorClient[dict[str, Any]] = AsyncIOMotorClient(MONGODB_URI)
db = client["scrape"]
return db["jobs"]
+26
View File
@@ -0,0 +1,26 @@
# STL
import logging
from typing import Any
# LOCAL
from api.backend.database import get_job_collection
LOG = logging.getLogger(__name__)
async def insert(item: dict[str, Any]) -> None:
collection = get_job_collection()
i = await collection.insert_one(item)
LOG.info(f"Inserted item: {i}")
async def query(filter: dict[str, Any]) -> list[dict[str, Any]]:
collection = get_job_collection()
cursor = collection.find(filter)
results: list[dict[str, Any]] = []
async for document in cursor:
del document["_id"]
results.append(document)
return results
+30
View File
@@ -0,0 +1,30 @@
# STL
from typing import Optional
# PDM
from pydantic import EmailStr, BaseModel
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
email: Optional[str] = None
class User(BaseModel):
email: EmailStr
full_name: Optional[str] = None
disabled: Optional[bool] = None
class UserInDB(User):
hashed_password: str
class UserCreate(BaseModel):
email: EmailStr
password: str
full_name: Optional[str] = None
+9 -4
View File
@@ -20,15 +20,20 @@ services:
- "--providers.docker=true"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
# - "--providers.file.filename=/etc/traefik/dynamic_conf.yaml"
ports:
- 80:80
- 443:443
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
# - "./dynamic_conf.yaml:/etc/traefik/dynamic_conf.yaml"
# - "/etc/letsencrypt/live/domain/fullchain.pem:/etc/certs/ssl-cert.pem"
# - "/etc/letsencrypt/live/domain/privkey.pem:/etc/certs/ssl-cert.key"
networks:
- web
mongo:
container_name: webscrape-mongo
image: mongo
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: example
networks:
- web
networks:
+8
View File
@@ -2,6 +2,14 @@
const nextConfig = {
output: "export",
distDir: "./dist",
async rewrites() {
return [
{
source: "/auth/:path*",
destination: "/api/auth/:path*",
},
];
},
};
export default nextConfig;
+90 -3
View File
@@ -19,9 +19,11 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.7.2",
"bootstrap": "^5.3.0",
"framer-motion": "^4.1.17",
"next": "^14.2.4",
"next-auth": "^4.24.7",
"react": "^18.3.1",
"react-bootstrap": "^2.8.0",
"react-dom": "^18.3.1",
@@ -7349,6 +7351,29 @@
"node": ">=4"
}
},
"node_modules/axios": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
"integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axios/node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/axobject-query": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
@@ -10489,9 +10514,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [
{
"type": "individual",
@@ -15291,6 +15316,33 @@
}
}
},
"node_modules/next-auth": {
"version": "4.24.7",
"resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.7.tgz",
"integrity": "sha512-iChjE8ov/1K/z98gdKbn2Jw+2vLgJtVV39X+rCP5SGnVQuco7QOr19FRNGMIrD8d3LYhHWV9j9sKLzq1aDWWQQ==",
"dependencies": {
"@babel/runtime": "^7.20.13",
"@panva/hkdf": "^1.0.2",
"cookie": "^0.5.0",
"jose": "^4.15.5",
"oauth": "^0.9.15",
"openid-client": "^5.4.0",
"preact": "^10.6.3",
"preact-render-to-string": "^5.1.19",
"uuid": "^8.3.2"
},
"peerDependencies": {
"next": "^12.2.5 || ^13 || ^14",
"nodemailer": "^6.6.5",
"react": "^17.0.2 || ^18",
"react-dom": "^17.0.2 || ^18"
},
"peerDependenciesMeta": {
"nodemailer": {
"optional": true
}
}
},
"node_modules/no-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
@@ -15372,6 +15424,11 @@
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz",
"integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ=="
},
"node_modules/oauth": {
"version": "0.9.15",
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
"integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA=="
},
"node_modules/oauth4webapi": {
"version": "2.11.1",
"resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.11.1.tgz",
@@ -17183,6 +17240,31 @@
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
"node_modules/preact": {
"version": "10.22.1",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.22.1.tgz",
"integrity": "sha512-jRYbDDgMpIb5LHq3hkI0bbl+l/TQ9UnkdQ0ww+lp+4MMOdqaUYdFc5qeyP+IV8FAd/2Em7drVPeKdQxsiWCf/A==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/preact-render-to-string": {
"version": "5.2.6",
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.6.tgz",
"integrity": "sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==",
"dependencies": {
"pretty-format": "^3.8.0"
},
"peerDependencies": {
"preact": ">=10"
}
},
"node_modules/preact-render-to-string/node_modules/pretty-format": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew=="
},
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -17321,6 +17403,11 @@
"node": ">= 0.10"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
+2
View File
@@ -14,9 +14,11 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.7.2",
"bootstrap": "^5.3.0",
"framer-motion": "^4.1.17",
"next": "^14.2.4",
"next-auth": "^4.24.7",
"react": "^18.3.1",
"react-bootstrap": "^2.8.0",
"react-dom": "^18.3.1",
Generated
+383 -10
View File
@@ -5,7 +5,7 @@
groups = ["default", "dev"]
strategy = ["cross_platform", "inherit_metadata"]
lock_version = "4.4.2"
content_hash = "sha256:a12cdcf1cdd6f91260a7d7126be4581a6820caf91ffc26386abfe9a6b3fbc9d9"
content_hash = "sha256:acc35177b2d4e7bdadd428e489a200c53caa5ea1455c442b4d9d738a60dcab77"
[[package]]
name = "aiohttp"
@@ -122,6 +122,20 @@ files = [
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
]
[[package]]
name = "asgiref"
version = "3.8.1"
requires_python = ">=3.8"
summary = "ASGI specs, helper code, and adapters"
groups = ["default"]
dependencies = [
"typing-extensions>=4; python_version < \"3.11\"",
]
files = [
{file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"},
{file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"},
]
[[package]]
name = "asttokens"
version = "2.4.1"
@@ -135,6 +149,16 @@ files = [
{file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"},
]
[[package]]
name = "async-property"
version = "0.2.2"
summary = "Python decorator for async properties."
groups = ["default"]
files = [
{file = "async_property-0.2.2-py2.py3-none-any.whl", hash = "sha256:8924d792b5843994537f8ed411165700b27b2bd966cefc4daeefc1253442a9d7"},
{file = "async_property-0.2.2.tar.gz", hash = "sha256:17d9bd6ca67e27915a75d92549df64b5c7174e9dc806b30a3934dc4ff0506380"},
]
[[package]]
name = "async-timeout"
version = "4.0.3"
@@ -168,6 +192,42 @@ files = [
{file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"},
]
[[package]]
name = "bcrypt"
version = "4.1.3"
requires_python = ">=3.7"
summary = "Modern password hashing for your software and your servers"
groups = ["default"]
files = [
{file = "bcrypt-4.1.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:48429c83292b57bf4af6ab75809f8f4daf52aa5d480632e53707805cc1ce9b74"},
{file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a8bea4c152b91fd8319fef4c6a790da5c07840421c2b785084989bf8bbb7455"},
{file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d3b317050a9a711a5c7214bf04e28333cf528e0ed0ec9a4e55ba628d0f07c1a"},
{file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:094fd31e08c2b102a14880ee5b3d09913ecf334cd604af27e1013c76831f7b05"},
{file = "bcrypt-4.1.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4fb253d65da30d9269e0a6f4b0de32bd657a0208a6f4e43d3e645774fb5457f3"},
{file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:193bb49eeeb9c1e2db9ba65d09dc6384edd5608d9d672b4125e9320af9153a15"},
{file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:8cbb119267068c2581ae38790e0d1fbae65d0725247a930fc9900c285d95725d"},
{file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6cac78a8d42f9d120b3987f82252bdbeb7e6e900a5e1ba37f6be6fe4e3848286"},
{file = "bcrypt-4.1.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01746eb2c4299dd0ae1670234bf77704f581dd72cc180f444bfe74eb80495b64"},
{file = "bcrypt-4.1.3-cp37-abi3-win32.whl", hash = "sha256:037c5bf7c196a63dcce75545c8874610c600809d5d82c305dd327cd4969995bf"},
{file = "bcrypt-4.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:8a893d192dfb7c8e883c4576813bf18bb9d59e2cfd88b68b725990f033f1b978"},
{file = "bcrypt-4.1.3-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d4cf6ef1525f79255ef048b3489602868c47aea61f375377f0d00514fe4a78c"},
{file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5698ce5292a4e4b9e5861f7e53b1d89242ad39d54c3da451a93cac17b61921a"},
{file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec3c2e1ca3e5c4b9edb94290b356d082b721f3f50758bce7cce11d8a7c89ce84"},
{file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3a5be252fef513363fe281bafc596c31b552cf81d04c5085bc5dac29670faa08"},
{file = "bcrypt-4.1.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5f7cd3399fbc4ec290378b541b0cf3d4398e4737a65d0f938c7c0f9d5e686611"},
{file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:c4c8d9b3e97209dd7111bf726e79f638ad9224b4691d1c7cfefa571a09b1b2d6"},
{file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:31adb9cbb8737a581a843e13df22ffb7c84638342de3708a98d5c986770f2834"},
{file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:551b320396e1d05e49cc18dd77d970accd52b322441628aca04801bbd1d52a73"},
{file = "bcrypt-4.1.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6717543d2c110a155e6821ce5670c1f512f602eabb77dba95717ca76af79867d"},
{file = "bcrypt-4.1.3-cp39-abi3-win32.whl", hash = "sha256:6004f5229b50f8493c49232b8e75726b568535fd300e5039e255d919fc3a07f2"},
{file = "bcrypt-4.1.3-cp39-abi3-win_amd64.whl", hash = "sha256:2505b54afb074627111b5a8dc9b6ae69d0f01fea65c2fcaea403448c503d3991"},
{file = "bcrypt-4.1.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:cb9c707c10bddaf9e5ba7cdb769f3e889e60b7d4fea22834b261f51ca2b89fed"},
{file = "bcrypt-4.1.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9f8ea645eb94fb6e7bea0cf4ba121c07a3a182ac52876493870033141aa687bc"},
{file = "bcrypt-4.1.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:f44a97780677e7ac0ca393bd7982b19dbbd8d7228c1afe10b128fd9550eef5f1"},
{file = "bcrypt-4.1.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d84702adb8f2798d813b17d8187d27076cca3cd52fe3686bb07a9083930ce650"},
{file = "bcrypt-4.1.3.tar.gz", hash = "sha256:2ee15dd749f5952fe3f0430d0ff6b74082e159c50332a1413d51b5689cf06623"},
]
[[package]]
name = "beautifulsoup4"
version = "4.12.3"
@@ -296,7 +356,7 @@ version = "1.16.0"
requires_python = ">=3.8"
summary = "Foreign Function Interface for Python calling C code."
groups = ["default"]
marker = "os_name == \"nt\" and implementation_name != \"pypy\""
marker = "os_name == \"nt\" and implementation_name != \"pypy\" or platform_python_implementation != \"PyPy\""
dependencies = [
"pycparser",
]
@@ -418,6 +478,50 @@ files = [
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "cryptography"
version = "42.0.8"
requires_python = ">=3.7"
summary = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
groups = ["default"]
dependencies = [
"cffi>=1.12; platform_python_implementation != \"PyPy\"",
]
files = [
{file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:81d8a521705787afe7a18d5bfb47ea9d9cc068206270aad0b96a725022e18d2e"},
{file = "cryptography-42.0.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:961e61cefdcb06e0c6d7e3a1b22ebe8b996eb2bf50614e89384be54c48c6b63d"},
{file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3ec3672626e1b9e55afd0df6d774ff0e953452886e06e0f1eb7eb0c832e8902"},
{file = "cryptography-42.0.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e599b53fd95357d92304510fb7bda8523ed1f79ca98dce2f43c115950aa78801"},
{file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5226d5d21ab681f432a9c1cf8b658c0cb02533eece706b155e5fbd8a0cdd3949"},
{file = "cryptography-42.0.8-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6b7c4f03ce01afd3b76cf69a5455caa9cfa3de8c8f493e0d3ab7d20611c8dae9"},
{file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:2346b911eb349ab547076f47f2e035fc8ff2c02380a7cbbf8d87114fa0f1c583"},
{file = "cryptography-42.0.8-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ad803773e9df0b92e0a817d22fd8a3675493f690b96130a5e24f1b8fabbea9c7"},
{file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2f66d9cd9147ee495a8374a45ca445819f8929a3efcd2e3df6428e46c3cbb10b"},
{file = "cryptography-42.0.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d45b940883a03e19e944456a558b67a41160e367a719833c53de6911cabba2b7"},
{file = "cryptography-42.0.8-cp37-abi3-win32.whl", hash = "sha256:a0c5b2b0585b6af82d7e385f55a8bc568abff8923af147ee3c07bd8b42cda8b2"},
{file = "cryptography-42.0.8-cp37-abi3-win_amd64.whl", hash = "sha256:57080dee41209e556a9a4ce60d229244f7a66ef52750f813bfbe18959770cfba"},
{file = "cryptography-42.0.8-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:dea567d1b0e8bc5764b9443858b673b734100c2871dc93163f58c46a97a83d28"},
{file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4783183f7cb757b73b2ae9aed6599b96338eb957233c58ca8f49a49cc32fd5e"},
{file = "cryptography-42.0.8-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0608251135d0e03111152e41f0cc2392d1e74e35703960d4190b2e0f4ca9c70"},
{file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dc0fdf6787f37b1c6b08e6dfc892d9d068b5bdb671198c72072828b80bd5fe4c"},
{file = "cryptography-42.0.8-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9c0c1716c8447ee7dbf08d6db2e5c41c688544c61074b54fc4564196f55c25a7"},
{file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fff12c88a672ab9c9c1cf7b0c80e3ad9e2ebd9d828d955c126be4fd3e5578c9e"},
{file = "cryptography-42.0.8-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cafb92b2bc622cd1aa6a1dce4b93307792633f4c5fe1f46c6b97cf67073ec961"},
{file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:31f721658a29331f895a5a54e7e82075554ccfb8b163a18719d342f5ffe5ecb1"},
{file = "cryptography-42.0.8-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b297f90c5723d04bcc8265fc2a0f86d4ea2e0f7ab4b6994459548d3a6b992a14"},
{file = "cryptography-42.0.8-cp39-abi3-win32.whl", hash = "sha256:2f88d197e66c65be5e42cd72e5c18afbfae3f741742070e3019ac8f4ac57262c"},
{file = "cryptography-42.0.8-cp39-abi3-win_amd64.whl", hash = "sha256:fa76fbb7596cc5839320000cdd5d0955313696d9511debab7ee7278fc8b5c84a"},
{file = "cryptography-42.0.8-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:ba4f0a211697362e89ad822e667d8d340b4d8d55fae72cdd619389fb5912eefe"},
{file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:81884c4d096c272f00aeb1f11cf62ccd39763581645b0812e99a91505fa48e0c"},
{file = "cryptography-42.0.8-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c9bb2ae11bfbab395bdd072985abde58ea9860ed84e59dbc0463a5d0159f5b71"},
{file = "cryptography-42.0.8-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7016f837e15b0a1c119d27ecd89b3515f01f90a8615ed5e9427e30d9cdbfed3d"},
{file = "cryptography-42.0.8-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5a94eccb2a81a309806027e1670a358b99b8fe8bfe9f8d329f27d72c094dde8c"},
{file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dec9b018df185f08483f294cae6ccac29e7a6e0678996587363dc352dc65c842"},
{file = "cryptography-42.0.8-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:343728aac38decfdeecf55ecab3264b015be68fc2816ca800db649607aeee648"},
{file = "cryptography-42.0.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:013629ae70b40af70c9a7a5db40abe5d9054e6f4380e50ce769947b73bf3caad"},
{file = "cryptography-42.0.8.tar.gz", hash = "sha256:8d09d05439ce7baa8e9e95b07ec5b6c886f548deb7e0f69ef25f64b3bce842f2"},
]
[[package]]
name = "cssselect"
version = "1.2.0"
@@ -440,6 +544,19 @@ files = [
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
]
[[package]]
name = "deprecation"
version = "2.1.0"
summary = "A library to handle automated deprecations"
groups = ["default"]
dependencies = [
"packaging",
]
files = [
{file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"},
{file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"},
]
[[package]]
name = "dnspython"
version = "2.6.1"
@@ -451,6 +568,20 @@ files = [
{file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"},
]
[[package]]
name = "ecdsa"
version = "0.19.0"
requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.6"
summary = "ECDSA cryptographic signature library (pure python)"
groups = ["default"]
dependencies = [
"six>=1.9.0",
]
files = [
{file = "ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a"},
{file = "ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8"},
]
[[package]]
name = "email-validator"
version = "2.1.1"
@@ -550,6 +681,40 @@ files = [
{file = "fastapi_cli-0.0.3.tar.gz", hash = "sha256:3b6e4d2c4daee940fb8db59ebbfd60a72c4b962bcf593e263e4cc69da4ea3d7f"},
]
[[package]]
name = "fastapi-keycloak"
version = "1.0.11"
requires_python = ">=3.8"
summary = "Keycloak API Client for integrating authentication and authorization with FastAPI"
groups = ["default"]
dependencies = [
"anyio>=3.4.0",
"asgiref>=3.4.1",
"certifi>=2021.10.8",
"charset-normalizer>=2.0.9",
"click>=8.0.3",
"ecdsa>=0.17.0",
"fastapi>=0.70.1",
"h11>=0.12.0",
"idna>=3.3",
"itsdangerous>=2.0.1",
"pyasn1>=0.4.8",
"pydantic>=1.5a1",
"python-jose>=3.3.0",
"requests>=2.26.0",
"rsa>=4.8",
"six>=1.16.0",
"sniffio>=1.2.0",
"starlette>=0.16.0",
"typing-extensions>=4.0.1",
"urllib3>=1.26.7",
"uvicorn>=0.16.0",
]
files = [
{file = "fastapi_keycloak-1.0.11-py3-none-any.whl", hash = "sha256:2d2da95cc9bfd2c02f80bbff3bd4ed985d398a07354da917ee4f7e6416373b29"},
{file = "fastapi_keycloak-1.0.11.tar.gz", hash = "sha256:e3b4e290bfbc47077cb9bca33f6bac758216a40bfd9fe13c843c71d56087e20c"},
]
[[package]]
name = "frozenlist"
version = "1.4.1"
@@ -716,6 +881,17 @@ files = [
{file = "ipython-8.26.0.tar.gz", hash = "sha256:1cec0fbba8404af13facebe83d04436a7434c7400e59f47acf467c64abd0956c"},
]
[[package]]
name = "itsdangerous"
version = "2.2.0"
requires_python = ">=3.8"
summary = "Safely pass data to untrusted environments and back."
groups = ["default"]
files = [
{file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"},
{file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"},
]
[[package]]
name = "jedi"
version = "0.19.1"
@@ -755,6 +931,21 @@ files = [
{file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"},
]
[[package]]
name = "jwcrypto"
version = "1.5.6"
requires_python = ">= 3.8"
summary = "Implementation of JOSE Web standards"
groups = ["default"]
dependencies = [
"cryptography>=3.4",
"typing-extensions>=4.5.0",
]
files = [
{file = "jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789"},
{file = "jwcrypto-1.5.6.tar.gz", hash = "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039"},
]
[[package]]
name = "lxml"
version = "5.2.2"
@@ -1056,6 +1247,35 @@ files = [
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
]
[[package]]
name = "motor"
version = "3.5.0"
requires_python = ">=3.8"
summary = "Non-blocking MongoDB driver for Tornado or asyncio"
groups = ["default"]
dependencies = [
"pymongo<5,>=4.5",
]
files = [
{file = "motor-3.5.0-py3-none-any.whl", hash = "sha256:e8f1d7a3370e8dd30eb4c68aaaee46dc608fbac70a757e58f3e828124f5e7693"},
{file = "motor-3.5.0.tar.gz", hash = "sha256:2b38e405e5a0c52d499edb8d23fa029debdf0158da092c21b44d92cac7f59942"},
]
[[package]]
name = "motor"
version = "3.5.0"
extras = ["asyncio"]
requires_python = ">=3.8"
summary = "Non-blocking MongoDB driver for Tornado or asyncio"
groups = ["default"]
dependencies = [
"motor==3.5.0",
]
files = [
{file = "motor-3.5.0-py3-none-any.whl", hash = "sha256:e8f1d7a3370e8dd30eb4c68aaaee46dc608fbac70a757e58f3e828124f5e7693"},
{file = "motor-3.5.0.tar.gz", hash = "sha256:2b38e405e5a0c52d499edb8d23fa029debdf0158da092c21b44d92cac7f59942"},
]
[[package]]
name = "multidict"
version = "6.0.5"
@@ -1391,6 +1611,31 @@ files = [
{file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"},
]
[[package]]
name = "passlib"
version = "1.7.4"
summary = "comprehensive password hashing framework supporting over 30 schemes"
groups = ["default"]
files = [
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
{file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
]
[[package]]
name = "passlib"
version = "1.7.4"
extras = ["bcrypt"]
summary = "comprehensive password hashing framework supporting over 30 schemes"
groups = ["default"]
dependencies = [
"bcrypt>=3.1.0",
"passlib==1.7.4",
]
files = [
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
{file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
]
[[package]]
name = "pexpect"
version = "4.9.0"
@@ -1440,13 +1685,24 @@ files = [
{file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"},
]
[[package]]
name = "pyasn1"
version = "0.6.0"
requires_python = ">=3.8"
summary = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
groups = ["default"]
files = [
{file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"},
{file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"},
]
[[package]]
name = "pycparser"
version = "2.22"
requires_python = ">=3.8"
summary = "C parser in Python"
groups = ["default"]
marker = "os_name == \"nt\" and implementation_name != \"pypy\""
marker = "os_name == \"nt\" and implementation_name != \"pypy\" or platform_python_implementation != \"PyPy\""
files = [
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
@@ -1571,6 +1827,46 @@ files = [
{file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
]
[[package]]
name = "pymongo"
version = "4.8.0"
requires_python = ">=3.8"
summary = "Python driver for MongoDB <http://www.mongodb.org>"
groups = ["default"]
dependencies = [
"dnspython<3.0.0,>=1.16.0",
]
files = [
{file = "pymongo-4.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2b7bec27e047e84947fbd41c782f07c54c30c76d14f3b8bf0c89f7413fac67a"},
{file = "pymongo-4.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c68fe128a171493018ca5c8020fc08675be130d012b7ab3efe9e22698c612a1"},
{file = "pymongo-4.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:920d4f8f157a71b3cb3f39bc09ce070693d6e9648fb0e30d00e2657d1dca4e49"},
{file = "pymongo-4.8.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52b4108ac9469febba18cea50db972605cc43978bedaa9fea413378877560ef8"},
{file = "pymongo-4.8.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:180d5eb1dc28b62853e2f88017775c4500b07548ed28c0bd9c005c3d7bc52526"},
{file = "pymongo-4.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aec2b9088cdbceb87e6ca9c639d0ff9b9d083594dda5ca5d3c4f6774f4c81b33"},
{file = "pymongo-4.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0cf61450feadca81deb1a1489cb1a3ae1e4266efd51adafecec0e503a8dcd84"},
{file = "pymongo-4.8.0-cp310-cp310-win32.whl", hash = "sha256:8b18c8324809539c79bd6544d00e0607e98ff833ca21953df001510ca25915d1"},
{file = "pymongo-4.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e5df28f74002e37bcbdfdc5109799f670e4dfef0fb527c391ff84f078050e7b5"},
{file = "pymongo-4.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6b50040d9767197b77ed420ada29b3bf18a638f9552d80f2da817b7c4a4c9c68"},
{file = "pymongo-4.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:417369ce39af2b7c2a9c7152c1ed2393edfd1cbaf2a356ba31eb8bcbd5c98dd7"},
{file = "pymongo-4.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf821bd3befb993a6db17229a2c60c1550e957de02a6ff4dd0af9476637b2e4d"},
{file = "pymongo-4.8.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9365166aa801c63dff1a3cb96e650be270da06e3464ab106727223123405510f"},
{file = "pymongo-4.8.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc8b8582f4209c2459b04b049ac03c72c618e011d3caa5391ff86d1bda0cc486"},
{file = "pymongo-4.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e5019f75f6827bb5354b6fef8dfc9d6c7446894a27346e03134d290eb9e758"},
{file = "pymongo-4.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b5802151fc2b51cd45492c80ed22b441d20090fb76d1fd53cd7760b340ff554"},
{file = "pymongo-4.8.0-cp311-cp311-win32.whl", hash = "sha256:4bf58e6825b93da63e499d1a58de7de563c31e575908d4e24876234ccb910eba"},
{file = "pymongo-4.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:b747c0e257b9d3e6495a018309b9e0c93b7f0d65271d1d62e572747f4ffafc88"},
{file = "pymongo-4.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e6a720a3d22b54183352dc65f08cd1547204d263e0651b213a0a2e577e838526"},
{file = "pymongo-4.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:31e4d21201bdf15064cf47ce7b74722d3e1aea2597c6785882244a3bb58c7eab"},
{file = "pymongo-4.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b804bb4f2d9dc389cc9e827d579fa327272cdb0629a99bfe5b83cb3e269ebf"},
{file = "pymongo-4.8.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2fbdb87fe5075c8beb17a5c16348a1ea3c8b282a5cb72d173330be2fecf22f5"},
{file = "pymongo-4.8.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd39455b7ee70aabee46f7399b32ab38b86b236c069ae559e22be6b46b2bbfc4"},
{file = "pymongo-4.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:940d456774b17814bac5ea7fc28188c7a1338d4a233efbb6ba01de957bded2e8"},
{file = "pymongo-4.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:236bbd7d0aef62e64caf4b24ca200f8c8670d1a6f5ea828c39eccdae423bc2b2"},
{file = "pymongo-4.8.0-cp312-cp312-win32.whl", hash = "sha256:47ec8c3f0a7b2212dbc9be08d3bf17bc89abd211901093e3ef3f2adea7de7a69"},
{file = "pymongo-4.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e84bc7707492f06fbc37a9f215374d2977d21b72e10a67f1b31893ec5a140ad8"},
{file = "pymongo-4.8.0.tar.gz", hash = "sha256:454f2295875744dc70f1881e4b2eb99cdad008a33574bc8aaf120530f66c0cde"},
]
[[package]]
name = "pyppeteer"
version = "0.0.25"
@@ -1638,6 +1934,55 @@ files = [
{file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
]
[[package]]
name = "python-jose"
version = "3.3.0"
summary = "JOSE implementation in Python"
groups = ["default"]
dependencies = [
"ecdsa!=0.15",
"pyasn1",
"rsa",
]
files = [
{file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"},
{file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"},
]
[[package]]
name = "python-jose"
version = "3.3.0"
extras = ["cryptography"]
summary = "JOSE implementation in Python"
groups = ["default"]
dependencies = [
"cryptography>=3.4.0",
"python-jose==3.3.0",
]
files = [
{file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"},
{file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"},
]
[[package]]
name = "python-keycloak"
version = "4.2.0"
requires_python = "<4.0,>=3.8"
summary = "python-keycloak is a Python package providing access to the Keycloak API."
groups = ["default"]
dependencies = [
"async-property>=0.2.2",
"deprecation>=2.1.0",
"httpx>=0.23.2",
"jwcrypto>=1.5.4",
"requests-toolbelt>=0.6.0",
"requests>=2.20.0",
]
files = [
{file = "python_keycloak-4.2.0-py3-none-any.whl", hash = "sha256:95761a589687f9308db63eb3e390c2822714372fa05b36cb59bfb02686c81315"},
{file = "python_keycloak-4.2.0.tar.gz", hash = "sha256:6eb4a9e0cf978b1fe8bacd47a8183e925dc72ccc26cac5b03a0d1a85972cc788"},
]
[[package]]
name = "python-multipart"
version = "0.0.9"
@@ -1729,6 +2074,20 @@ files = [
{file = "requests_html-0.10.0-py3-none-any.whl", hash = "sha256:cb8a78cf829c4eca9d6233f28524f65dd2bfaafb4bdbbc407f0a0b8f487df6e2"},
]
[[package]]
name = "requests-toolbelt"
version = "1.0.0"
requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
summary = "A utility belt for advanced users of python-requests"
groups = ["default"]
dependencies = [
"requests<3.0.0,>=2.0.1",
]
files = [
{file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"},
{file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"},
]
[[package]]
name = "rich"
version = "13.7.1"
@@ -1744,6 +2103,20 @@ files = [
{file = "rich-13.7.1.tar.gz", hash = "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432"},
]
[[package]]
name = "rsa"
version = "4.9"
requires_python = ">=3.6,<4"
summary = "Pure-Python RSA implementation"
groups = ["default"]
dependencies = [
"pyasn1>=0.1.3",
]
files = [
{file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"},
{file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"},
]
[[package]]
name = "s3transfer"
version = "0.10.2"
@@ -2078,7 +2451,7 @@ files = [
[[package]]
name = "uvicorn"
version = "0.29.0"
version = "0.30.1"
requires_python = ">=3.8"
summary = "The lightning-fast ASGI server."
groups = ["default"]
@@ -2088,13 +2461,13 @@ dependencies = [
"typing-extensions>=4.0; python_version < \"3.11\"",
]
files = [
{file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"},
{file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"},
{file = "uvicorn-0.30.1-py3-none-any.whl", hash = "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81"},
{file = "uvicorn-0.30.1.tar.gz", hash = "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8"},
]
[[package]]
name = "uvicorn"
version = "0.29.0"
version = "0.30.1"
extras = ["standard"]
requires_python = ">=3.8"
summary = "The lightning-fast ASGI server."
@@ -2104,14 +2477,14 @@ dependencies = [
"httptools>=0.5.0",
"python-dotenv>=0.13",
"pyyaml>=5.1",
"uvicorn==0.29.0",
"uvicorn==0.30.1",
"uvloop!=0.15.0,!=0.15.1,>=0.14.0; (sys_platform != \"cygwin\" and sys_platform != \"win32\") and platform_python_implementation != \"PyPy\"",
"watchfiles>=0.13",
"websockets>=10.4",
]
files = [
{file = "uvicorn-0.29.0-py3-none-any.whl", hash = "sha256:2c2aac7ff4f4365c206fd773a39bf4ebd1047c238f8b8268ad996829323473de"},
{file = "uvicorn-0.29.0.tar.gz", hash = "sha256:6a69214c0b6a087462412670b3ef21224fa48cae0e452b5883e8e8bdfdd11dd0"},
{file = "uvicorn-0.30.1-py3-none-any.whl", hash = "sha256:cd17daa7f3b9d7a24de3617820e634d0933b69eed8e33a516071174427238c81"},
{file = "uvicorn-0.30.1.tar.gz", hash = "sha256:d46cd8e0fd80240baffbcd9ec1012a712938754afcf81bce56c024c1656aece8"},
]
[[package]]
+8 -1
View File
@@ -6,7 +6,7 @@ authors = [
{name = "Jayden Pyles", email = "jpylesbuisness@gmail.com"},
]
dependencies = [
"uvicorn>=0.29.0",
"uvicorn>=0.30.1",
"fastapi>=0.111.0",
"boto3>=1.34.140",
"python-dotenv>=1.0.1",
@@ -24,6 +24,12 @@ dependencies = [
"pandas>=2.2.2",
"openpyxl>=3.1.5",
"xlsxwriter>=3.2.0",
"python-keycloak>=4.2.0",
"fastapi-keycloak>=1.0.11",
"pymongo>=4.8.0",
"motor[asyncio]>=3.5.0",
"python-jose[cryptography]>=3.3.0",
"passlib[bcrypt]>=1.7.4",
]
requires-python = ">=3.10"
readme = "README.md"
@@ -47,6 +53,7 @@ reportUnknownMemberType= false
reportMissingImports = true
reportMissingTypeStubs = false
reportAny = false
reportCallInDefaultInitializer = false
pythonVersion = "3.9"
pythonPlatform = "Linux"
+9 -20
View File
@@ -1,6 +1,5 @@
import React, { useState } from "react";
import { useAuth } from "../useAuth";
import { LogoutOptions, RedirectLoginOptions } from "@auth0/auth0-react";
import { useAuth } from "../hooks/useAuth";
import {
Box,
Drawer,
@@ -22,20 +21,10 @@ import { useRouter } from "next/router";
const NavDrawer: React.FC = () => {
const router = useRouter();
const { loginWithRedirect, logout, user, isAuthenticated } = useAuth();
const { login, logout, user, isAuthenticated } = useAuth();
const [open, setOpen] = useState<boolean>(false);
const handleLogout = () => {
const logoutOptions: LogoutOptions = {};
logout(logoutOptions);
};
const handleLogin = () => {
const loginOptions: RedirectLoginOptions = {
authorizationParams: { redirect_uri: "http://localhost" },
};
loginWithRedirect(loginOptions);
};
console.log(user);
const toggleDrawer =
(open: boolean) => (event: React.KeyboardEvent | React.MouseEvent) => {
@@ -80,7 +69,7 @@ const NavDrawer: React.FC = () => {
return (
<>
<AppBar position="static">
<Toolbar>
<Toolbar className="flex flex-row justify-between items-center">
<IconButton
edge="start"
color="inherit"
@@ -90,16 +79,16 @@ const NavDrawer: React.FC = () => {
<MenuIcon />
</IconButton>
{isAuthenticated ? (
<>
<div className="flex flex-row items-center">
<Typography variant="body1" sx={{ marginRight: 2 }}>
Welcome, {user?.name}
Welcome, {user?.full_name}
</Typography>
<Button color="inherit" onClick={handleLogout}>
<Button color="inherit" onClick={logout}>
Logout
</Button>
</>
</div>
) : (
<Button color="inherit" onClick={handleLogin}>
<Button color="inherit" onClick={() => router.push("/login")}>
Login
</Button>
)}
+52
View File
@@ -0,0 +1,52 @@
import { useState, useEffect } from "react";
import axios from "axios";
type User = {
full_name: string;
email: string;
disabled: null | boolean;
};
export const useAuth = () => {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
const fetchUserInfo = async () => {
try {
const response = await axios.get("/api/auth/users/me", {
headers: {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
});
setUser(response.data);
setIsAuthenticated(true);
} catch (error) {
setIsAuthenticated(false);
setUser(null);
}
};
const token = localStorage.getItem("token");
if (token) {
fetchUserInfo();
}
}, []);
const login = () => {
window.location.href = "http://localhost:8000/api/auth/login";
};
const logout = () => {
localStorage.removeItem("token");
setIsAuthenticated(false);
setUser(null);
};
return {
isAuthenticated,
user,
login,
logout,
};
};
-19
View File
@@ -1,19 +0,0 @@
import { initAuth0 } from "@auth0/nextjs-auth0";
const secret = process.env.AUTH0_SECRET;
if (!secret) {
throw new Error("Secret not found");
}
export default initAuth0({
secret: process.env.AUTH0_SECRET as string,
baseURL: process.env.AUTH0_BASE_URL as string,
issuerBaseURL: process.env.AUTH0_ISSUER_BASE_URL as string,
clientID: process.env.AUTH0_CLIENT_ID as string,
clientSecret: process.env.AUTH0_CLIENT_SECRET as string,
routes: {
callback: "/auth/callback",
postLogoutRedirect: "/",
},
});
+4 -17
View File
@@ -4,32 +4,19 @@ import "../styles/globals.css";
import React from "react";
import type { AppProps } from "next/app";
import Head from "next/head";
import { Auth0Provider } from "@auth0/auth0-react";
import { SessionProvider } from "next-auth/react";
import NavDrawer from "../components/NavDrawer";
const domain = process.env.NEXT_PUBLIC_AUTH0_ISSUER_BASE_URL || "";
const clientId = process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID || "";
console.log(domain);
const App: React.FC<AppProps> = ({ Component, pageProps }) => {
return (
<>
<Head>
<title>Webapp Template</title>
</Head>
<Auth0Provider
domain={domain}
clientId={clientId}
authorizationParams={{
redirect_uri: "http://localhost",
}}
cacheLocation="localstorage"
useRefreshTokens={true}
>
<NavDrawer></NavDrawer>
<SessionProvider session={pageProps.session}>
<NavDrawer />
<Component {...pageProps} />
</Auth0Provider>
</SessionProvider>
</>
);
};
+124 -48
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useRef } from "react";
import {
Typography,
TextField,
@@ -10,9 +10,11 @@ import {
TableRow,
Container,
Box,
IconButton,
Tooltip,
} from "@mui/material";
import AddIcon from "@mui/icons-material/Add";
import { useAuth0 } from "@auth0/auth0-react";
import { useAuth } from "../hooks/useAuth";
import { useRouter } from "next/router";
interface Element {
@@ -31,13 +33,24 @@ type Result = {
[key: string]: ScrapeResult[];
};
function validateURL(url: string): boolean {
try {
new URL(url);
return true;
} catch (_) {
return false;
}
}
const Home = () => {
const { user } = useAuth0();
const { user } = useAuth();
const router = useRouter();
const { elements, url } = router.query;
const [submittedURL, setUrl] = useState("");
const [submittedURL, setSubmittedURL] = useState("");
const [isValidURL, setIsValidUrl] = useState<boolean>(true);
const [urlError, setUrlError] = useState<string | null>(null);
const [rows, setRows] = useState<Element[]>([]);
const [results, setResults] = useState<null | Result>(null);
const [newRow, setNewRow] = useState<Element>({
@@ -46,29 +59,54 @@ const Home = () => {
url: "",
});
const resultsRef = useRef<HTMLTableElement | null>(null);
useEffect(() => {
if (elements) {
setRows(JSON.parse(elements as string));
}
if (url) {
setUrl(url as string);
setSubmittedURL(url as string);
}
}, [elements, url]);
useEffect(() => {
if (results && resultsRef.current) {
resultsRef.current.scrollIntoView({ behavior: "smooth" });
}
}, [results]);
const handleAddRow = () => {
newRow.url = submittedURL;
setRows([...rows, newRow]);
const updatedRow = { ...newRow, url: submittedURL };
setRows([...rows, updatedRow]);
setNewRow({ name: "", xpath: "", url: "" });
};
const handleDeleteRow = (elementName: string) => {
setRows(
rows.filter((r) => {
return elementName !== r.name;
}),
);
};
const handleSubmit = () => {
if (!validateURL(submittedURL)) {
setIsValidUrl(false);
setUrlError("Please enter a valid URL.");
return;
}
setIsValidUrl(true);
setUrlError(null);
fetch("/api/submit-scrape-job", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
url: url,
url: submittedURL,
elements: rows,
user: user?.name,
user: user?.email,
time_created: new Date().toISOString(),
}),
})
@@ -77,25 +115,43 @@ const Home = () => {
};
return (
<>
<Box
display="flex"
flexDirection="column"
justifyContent="center"
alignItems="center"
minHeight="100vh"
py={4}
>
<Container maxWidth="md">
<Typography variant="h1" gutterBottom>
<Typography variant="h1" gutterBottom textAlign="center">
Web Scraper
</Typography>
<div style={{ marginBottom: "20px" }}>
<div
style={{ marginBottom: "20px" }}
className="flex flex-row space-x-4 items-center"
>
<TextField
label="URL"
variant="outlined"
fullWidth
value={url}
onChange={(e) => setUrl(e.target.value)}
style={{ marginBottom: "10px" }}
value={submittedURL}
onChange={(e) => setSubmittedURL(e.target.value)}
error={!isValidURL}
helperText={!isValidURL ? urlError : ""}
/>
<Button variant="contained" color="primary" onClick={handleSubmit}>
<Button
className="!text-black"
variant="contained"
color="primary"
size="small"
onClick={handleSubmit}
disabled={!(rows.length > 0)}
>
Submit
</Button>
</div>
<Box display="flex" gap={2} marginBottom={2}>
<Box display="flex" gap={2} marginBottom={2} className="items-center">
<TextField
label="Name"
variant="outlined"
@@ -110,17 +166,29 @@ const Home = () => {
value={newRow.xpath}
onChange={(e) => setNewRow({ ...newRow, xpath: e.target.value })}
/>
<Button
variant="contained"
color="secondary"
startIcon={<AddIcon />}
onClick={handleAddRow}
<Tooltip
title={
newRow.xpath.length > 0 && newRow.name.length > 0
? "Add Element"
: "Fill out all fields to add an element"
}
placement="top"
>
Add Elements
</Button>
<span>
<IconButton
aria-label="add"
size="small"
onClick={handleAddRow}
sx={{ height: "40px", width: "40px" }}
disabled={!(newRow.xpath.length > 0 && newRow.name.length > 0)}
>
<AddIcon fontSize="inherit" sx={{ color: "black" }} />
</IconButton>
</span>
</Tooltip>
</Box>
<Typography variant="h4">Elements</Typography>
<Table>
<Table className="mb-4">
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
@@ -136,36 +204,44 @@ const Home = () => {
<TableCell>
<TextField variant="outlined" fullWidth value={row.xpath} />
</TableCell>
<TableCell>
<Button onClick={() => handleDeleteRow(row.name)}>
Delete
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{results && (
<Table style={{ marginTop: "20px" }}>
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell>XPath</TableCell>
<TableCell>Text</TableCell>
</TableRow>
</TableHead>
<TableBody>
{Object.keys(results).map((key, index) => (
<React.Fragment key={index}>
{results[key].map((result, resultIndex) => (
<TableRow key={resultIndex}>
<TableCell>{result.name}</TableCell>
<TableCell>{result.xpath}</TableCell>
<TableCell>{result.text}</TableCell>
</TableRow>
))}
</React.Fragment>
))}
</TableBody>
</Table>
<>
<Typography variant="h4">Results</Typography>
<Table ref={resultsRef} style={{ marginTop: "20px" }}>
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell>XPath</TableCell>
<TableCell>Text</TableCell>
</TableRow>
</TableHead>
<TableBody>
{Object.keys(results).map((key, index) => (
<React.Fragment key={index}>
{results[key].map((result, resultIndex) => (
<TableRow key={resultIndex}>
<TableCell>{result.name}</TableCell>
<TableCell>{result.xpath}</TableCell>
<TableCell>{result.text}</TableCell>
</TableRow>
))}
</React.Fragment>
))}
</TableBody>
</Table>
</>
)}
</Container>
</>
</Box>
);
};
+13 -15
View File
@@ -1,26 +1,24 @@
import { useAuth0 } from "@auth0/auth0-react";
import React, { useEffect, useState } from "react";
import JobTable from "../components/JobTable";
import { useAuth } from "../hooks/useAuth";
const Jobs = () => {
const { user } = useAuth0();
const { user } = useAuth();
const [jobs, setJobs] = useState([]);
useEffect(() => {
fetch("/api/retrieve-scrape-jobs", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ user: user?.name }),
})
.then((response) => response.json())
.then((data) => setJobs(data));
}, []);
if (user) {
fetch("/api/retrieve-scrape-jobs", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ user: user?.email }),
})
.then((response) => response.json())
.then((data) => setJobs(data));
}
}, [user]);
return (
<>
<JobTable jobs={jobs} />
</>
);
return <JobTable jobs={jobs} />;
};
export default Jobs;
+122
View File
@@ -0,0 +1,122 @@
import React, { useState } from "react";
import axios from "axios";
import { Button, TextField, Typography, Container, Box } from "@mui/material";
type Mode = "login" | "signup";
const AuthForm: React.FC = () => {
const [mode, setMode] = useState<Mode>("login");
const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const [fullName, setFullName] = useState<string>("");
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
try {
if (mode === "login") {
const params = new URLSearchParams();
params.append("username", email);
params.append("password", password);
const response = await axios.post(
"http://localhost:8000/api/auth/token",
params,
);
localStorage.setItem("token", response.data.access_token);
alert("Login successful");
} else {
await axios.post("http://localhost:8000/api/auth/signup", {
email: email,
password: password,
full_name: fullName,
});
alert("Signup successful");
}
} catch (error) {
console.error(error);
alert(`${mode.charAt(0).toUpperCase() + mode.slice(1)} failed`);
}
};
const toggleMode = () => {
setMode((prevMode) => (prevMode === "login" ? "signup" : "login"));
};
return (
<Container component="main" maxWidth="xs">
<Box
sx={{
marginTop: 8,
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Typography component="h1" variant="h5">
{mode.charAt(0).toUpperCase() + mode.slice(1)}
</Typography>
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 3 }}>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
autoComplete="email"
autoFocus
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
autoComplete="current-password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
{mode === "signup" && (
<TextField
variant="outlined"
margin="normal"
required
fullWidth
name="fullName"
label="Full Name"
type="text"
id="fullName"
autoComplete="name"
value={fullName}
onChange={(e) => setFullName(e.target.value)}
/>
)}
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
sx={{ mt: 3, mb: 2 }}
>
{mode.charAt(0).toUpperCase() + mode.slice(1)}
</Button>
<Button
onClick={toggleMode}
fullWidth
variant="outlined"
color="secondary"
>
Switch to {mode === "login" ? "Signup" : "Login"}
</Button>
</Box>
</Box>
</Container>
);
};
export default AuthForm;
-13
View File
@@ -1,13 +0,0 @@
import { useAuth0 } from "@auth0/auth0-react";
export const useAuth = () => {
const { loginWithRedirect, logout, user, isAuthenticated, isLoading } =
useAuth0();
return {
loginWithRedirect,
logout,
user,
isAuthenticated,
isLoading,
};
};
+3 -12
View File
@@ -98,11 +98,7 @@
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"noEmit": true,
"incremental": true,
@@ -110,11 +106,6 @@
"resolveJsonModule": true,
"isolatedModules": true
},
"include": [
"src",
"src/declaration.d.ts"
],
"exclude": [
"node_modules"
]
"include": ["src", "src/declaration.d.ts", "src/next-auth.d.ts"],
"exclude": ["node_modules"]
}