mirror of
https://github.com/jaypyles/Scraperr.git
synced 2026-05-03 16:00:41 +00:00
feat: convert to self-hosted version
This commit is contained in:
+1
-1
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
const nextConfig = {
|
||||
output: "export",
|
||||
distDir: "./dist",
|
||||
async rewrites() {
|
||||
return [
|
||||
{
|
||||
source: "/auth/:path*",
|
||||
destination: "/api/auth/:path*",
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
Generated
+90
-3
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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
@@ -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"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user