Merge pull request 'poetry-migration' (#2) from poetry-migration into main
Reviewed-on: #2
This commit is contained in:
@@ -12,18 +12,21 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: Set up Python 3.11
|
- name: Set up Python 3.10
|
||||||
uses: actions/setup-python@v3
|
uses: actions/setup-python@v3
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.10"
|
||||||
|
|
||||||
|
- name: Install poetry
|
||||||
|
uses: abatilo/actions-poetry@v2
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
working-directory: server
|
working-directory: server
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
poetry install
|
||||||
pip install pytest
|
|
||||||
pip install -r requirements.txt
|
|
||||||
- name: Lint with black
|
- name: Lint with black
|
||||||
working-directory: server
|
working-directory: server
|
||||||
run: |
|
run: |
|
||||||
black --check .
|
ls -al
|
||||||
|
poetry run black --check .
|
||||||
|
|||||||
4
client/package-lock.json
generated
4
client/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "portfolio-front",
|
"name": "portfolio-front",
|
||||||
"version": "0.0.1",
|
"version": "0.0.7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "portfolio-front",
|
"name": "portfolio-front",
|
||||||
"version": "0.0.1",
|
"version": "0.0.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth0/auth0-react": "^2.2.4",
|
"@auth0/auth0-react": "^2.2.4",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
"@fortawesome/fontawesome-svg-core": "^6.5.1",
|
||||||
|
|||||||
2
server/.gitignore
vendored
2
server/.gitignore
vendored
@@ -174,3 +174,5 @@ poetry.toml
|
|||||||
pyrightconfig.json
|
pyrightconfig.json
|
||||||
|
|
||||||
# End of https://www.toptal.com/developers/gitignore/api/python
|
# End of https://www.toptal.com/developers/gitignore/api/python
|
||||||
|
|
||||||
|
.vscode/
|
||||||
@@ -1 +0,0 @@
|
|||||||
__version__ = "0.1.22"
|
|
||||||
@@ -5,24 +5,21 @@ CREATE TABLE `self` (
|
|||||||
`email` VARCHAR(255) NOT NULL,
|
`email` VARCHAR(255) NOT NULL,
|
||||||
`bio` TEXT NOT NULL,
|
`bio` TEXT NOT NULL,
|
||||||
`github` VARCHAR(255) NOT NULL,
|
`github` VARCHAR(255) NOT NULL,
|
||||||
`auth0_sub` VARCHAR(255) NOT NULL,
|
`gitea` VARCHAR(255) NOT NULL
|
||||||
`test_sub` VARCHAR(255) NOT NULL
|
|
||||||
);
|
);
|
||||||
INSERT INTO `self` (
|
INSERT INTO `self` (
|
||||||
`name`,
|
`name`,
|
||||||
`email`,
|
`email`,
|
||||||
`bio`,
|
`bio`,
|
||||||
`github`,
|
`github`,
|
||||||
`auth0_sub`,
|
`gitea`
|
||||||
`test_sub`
|
|
||||||
)
|
)
|
||||||
VALUES (
|
VALUES (
|
||||||
'Lucas Jensen',
|
'Lucas Jensen',
|
||||||
'lucas.p.jensen10@gmail.com',
|
'lucas@lucasjensen.me',
|
||||||
"I am a recent graduate from Oregon State University with a Bachelor's degree in Computer Science, driven by a passion for solving complex problems through technology. During my academic journey, I honed my skills and practical knowledge, setting a strong foundation for my career. My enthusiasm led me to a Software Engineering internship at Cvent, where I focused on Service Level Indicators (SLIs) and TypeScript. This experience allowed me to dive deep into the intricacies of backend development, gaining hands-on expertise in Python, FastAPI, Flask, Bash scripting, Linux, Nginx, and Systemd.\nMy commitment to delivering robust solutions is reflected in my proficiency in writing unit tests, ensuring the reliability and stability of the software I develop. I thrive in collaborative environments and have successfully contributed to team projects, understanding the importance of effective communication and cooperation. As I embark on my professional journey, I am excited to leverage my diverse skill set to tackle new challenges and make meaningful contributions to the field of computer science. Explore my portfolio to witness the intersection of my academic background and practical experiences that shape my identity as a dedicated and skilled computer scientist.",
|
"I'm a software engineer working for the University of Oregon and an alum from Oregon State University with a B.S. in Computer Science. I'm passionate about open source software and self hosting anything and everything. Most of my projects, including this gitea instance, are hosted either on a Raspberry Pi or an Ubuntu server with Linode.\nMuch of what you see here is a WIP and will likely remain so for the foreseeable future. All the projects listed here are passion projects which are tended to in my spare time.",
|
||||||
'https://github.com/ljensen505',
|
'https://github.com/ljensen505',
|
||||||
'google-oauth2|103593642272149633528',
|
'https://gitea.lucasjensen.me/lucasjensen'
|
||||||
'FZdDeArr7QuX8qVmbKD2ggdLvlJZKEjE@clients'
|
|
||||||
);
|
);
|
||||||
DROP TABLE IF EXISTS `projects`;
|
DROP TABLE IF EXISTS `projects`;
|
||||||
CREATE TABLE `projects` (
|
CREATE TABLE `projects` (
|
||||||
@@ -43,22 +40,22 @@ INSERT INTO `projects` (
|
|||||||
VALUES (
|
VALUES (
|
||||||
'The Grapefruits Duo',
|
'The Grapefruits Duo',
|
||||||
'An artist website for a local chamber music duo. Built with MySQL, FastAPI, and React with TypeScript.',
|
'An artist website for a local chamber music duo. Built with MySQL, FastAPI, and React with TypeScript.',
|
||||||
'https://github.com/ljensen505/TheGrapefruitsDuo',
|
'https://gitea.lucasjensen.me/lucasjensen/TheGrapefruitsDuo',
|
||||||
'https://thegrapefruitsduo.com/',
|
'https://thegrapefruitsduo.com/',
|
||||||
TRUE
|
TRUE
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
'Portfolio Backend',
|
'Portfolio Homepage',
|
||||||
'A RESTful API for my portfolio website. Consumed by the portfolio frontend. Built with FastAPI and MySQL. Hosted on a Raspberry Pi in my living room.',
|
'This very website!',
|
||||||
'https://github.com/ljensen505/portfolio-back',
|
'https://gitea.lucasjensen.me/lucasjensen/LucasJensen',
|
||||||
'https://api.lucasjensen.me/',
|
'https://lucasjensen.me/',
|
||||||
TRUE
|
TRUE
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
'Portfolio Frontend',
|
'Megan Johns Portfilo Website',
|
||||||
'The frontend for my portfolio website (this very site!). Consumes the portfolio backend. Built with React and Typescript. Hosted on a Raspberry Pi in my living room.',
|
'A comprehensive portfolio for local Eugene musician and artist, Megan Johns.',
|
||||||
'https://github.com/ljensen505/portfolio-front',
|
'https://gitea.lucasjensen.me/lucasjensen/MeganJohns',
|
||||||
'https://lucasjensen.me/',
|
'https://mj.lucasjensen.me/',
|
||||||
TRUE
|
TRUE
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@@ -74,18 +71,4 @@ VALUES (
|
|||||||
NULL,
|
NULL,
|
||||||
'https://efdl.lucasjensen.me/',
|
'https://efdl.lucasjensen.me/',
|
||||||
TRUE
|
TRUE
|
||||||
),
|
|
||||||
(
|
|
||||||
'Chess API',
|
|
||||||
'A RESTful API for playing chess online. Consumed by the Chess GUI.',
|
|
||||||
'https://github.com/ljensen505/chess-back',
|
|
||||||
'https://api.chess.v2.lucasjensen.me/',
|
|
||||||
TRUE
|
|
||||||
),
|
|
||||||
(
|
|
||||||
'Chess',
|
|
||||||
'A webapp for playing chess online against a friend. Consumes the Chess API.',
|
|
||||||
'https://github.com/ljensen505/chess-front',
|
|
||||||
'https://chess.lucasjensen.me/',
|
|
||||||
FALSE
|
|
||||||
)
|
)
|
||||||
@@ -6,6 +6,7 @@ origins = [
|
|||||||
"http://localhost",
|
"http://localhost",
|
||||||
"http://localhost:3000",
|
"http://localhost:3000",
|
||||||
"https://localhost:3000",
|
"https://localhost:3000",
|
||||||
|
"http://127.0.0.1:3000",
|
||||||
"https://lucasjensen.me/",
|
"https://lucasjensen.me/",
|
||||||
"https://lucasjensen.me",
|
"https://lucasjensen.me",
|
||||||
"https://www.lucasjensen.me/",
|
"https://www.lucasjensen.me/",
|
||||||
|
|||||||
@@ -6,14 +6,11 @@ from fastapi.middleware.cors import CORSMiddleware
|
|||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
import queries
|
import queries
|
||||||
from __version__ import __version__
|
|
||||||
from helpers import origins
|
from helpers import origins
|
||||||
from models import About, Project
|
from models import About, Project, Lucas
|
||||||
from utils import VerifyToken
|
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
auth = VerifyToken()
|
|
||||||
|
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
@@ -28,19 +25,9 @@ app.mount("/static", StaticFiles(directory="static"), name="static")
|
|||||||
|
|
||||||
|
|
||||||
@app.get("/", status_code=status.HTTP_200_OK)
|
@app.get("/", status_code=status.HTTP_200_OK)
|
||||||
async def root():
|
async def root() -> Lucas:
|
||||||
available_routes = [
|
lucas = Lucas(about=queries.get_about(), projects=queries.get_projects())
|
||||||
"/",
|
return lucas
|
||||||
"/about",
|
|
||||||
"/projects",
|
|
||||||
"/static/resume.pdf",
|
|
||||||
"/static/favicon.png",
|
|
||||||
]
|
|
||||||
return {
|
|
||||||
"welcome": "backend api for lucasjensen.me",
|
|
||||||
"version": __version__,
|
|
||||||
"routes": available_routes,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/about", status_code=status.HTTP_200_OK)
|
@app.get("/about", status_code=status.HTTP_200_OK)
|
||||||
@@ -69,11 +56,9 @@ async def projects() -> list[Project]:
|
|||||||
|
|
||||||
@app.get("/projects/{project_id}", status_code=status.HTTP_200_OK)
|
@app.get("/projects/{project_id}", status_code=status.HTTP_200_OK)
|
||||||
async def project(project_id: int) -> Project:
|
async def project(project_id: int) -> Project:
|
||||||
project = queries.get_project(project_id)
|
project = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
project = queries.get_project(project_id)
|
project = queries.get_project(project_id)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"err getting projects: {e}")
|
print(f"err getting projects: {e}")
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@@ -86,48 +71,3 @@ async def project(project_id: int) -> Project:
|
|||||||
detail=f"project with id {project_id} not found",
|
detail=f"project with id {project_id} not found",
|
||||||
)
|
)
|
||||||
return project
|
return project
|
||||||
|
|
||||||
|
|
||||||
@app.post("/projects", status_code=status.HTTP_201_CREATED)
|
|
||||||
async def post_project(project: Project, auth_result=Security(auth.verify)) -> Project:
|
|
||||||
user_sub, test_sub = queries.get_subs().values()
|
|
||||||
jwt_sub = auth_result.get("sub")
|
|
||||||
if jwt_sub not in [user_sub, test_sub]:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="unauthorized",
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
return queries.create_project(project)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"err creating project: {e}")
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail=f"database error: {e}",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.delete("/projects/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
||||||
async def delete_project(project_id: int, auth_result=Security(auth.verify)):
|
|
||||||
user_sub, test_sub = queries.get_subs().values()
|
|
||||||
jwt_sub = auth_result.get("sub")
|
|
||||||
if jwt_sub not in [user_sub, test_sub]:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="unauthorized",
|
|
||||||
)
|
|
||||||
project = queries.get_project(project_id)
|
|
||||||
|
|
||||||
if project is None:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
|
||||||
detail=f"project with id {project_id} not found",
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
return queries.delete_project(project_id)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"err deleting project: {e}")
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail=f"database error: {e}",
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
class About(BaseModel):
|
class About(BaseModel):
|
||||||
@@ -6,12 +7,18 @@ class About(BaseModel):
|
|||||||
email: str
|
email: str
|
||||||
bio: str
|
bio: str
|
||||||
github: str
|
github: str
|
||||||
|
gitea: str
|
||||||
|
|
||||||
|
|
||||||
class Project(BaseModel):
|
class Project(BaseModel):
|
||||||
id: int | None = None
|
id: Optional[int] = None
|
||||||
name: str
|
name: str
|
||||||
description: str
|
description: str
|
||||||
source: str | None = None
|
source: Optional[str] = None
|
||||||
live: str | None = None
|
live: Optional[str] = None
|
||||||
is_self_hosted: bool = False
|
is_self_hosted: Optional[bool] = False
|
||||||
|
|
||||||
|
|
||||||
|
class Lucas(BaseModel):
|
||||||
|
projects: list[Project]
|
||||||
|
about: About
|
||||||
|
|||||||
1312
server/poetry.lock
generated
Normal file
1312
server/poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
server/pyproject.toml
Normal file
22
server/pyproject.toml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "lucasjensen-fastapi"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "A RESTful API for lucasjensen.me"
|
||||||
|
authors = ["Lucas Jensen <lucas@lucasjensen.me>"]
|
||||||
|
readme = "README.md"
|
||||||
|
package-mode = false
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.10"
|
||||||
|
black = "^24.4.2"
|
||||||
|
pytest = "^8.2.2"
|
||||||
|
fastapi = "^0.111.0"
|
||||||
|
mysql-connector-python = "^8.4.0"
|
||||||
|
pyjwt = "^2.8.0"
|
||||||
|
pydantic = "^2.7.4"
|
||||||
|
pydantic-settings = "^2.3.4"
|
||||||
|
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
@@ -52,7 +52,7 @@ def delete_project(project_id: int) -> None:
|
|||||||
def get_about() -> About:
|
def get_about() -> About:
|
||||||
db = connect_db()
|
db = connect_db()
|
||||||
cursor = db.cursor(dictionary=True)
|
cursor = db.cursor(dictionary=True)
|
||||||
cursor.execute("SELECT name, email, bio, github FROM self")
|
cursor.execute("SELECT name, email, bio, github, gitea FROM self")
|
||||||
data = {key: val for key, val in cursor.fetchone().items()} # type: ignore
|
data = {key: val for key, val in cursor.fetchone().items()} # type: ignore
|
||||||
db.close()
|
db.close()
|
||||||
return About(**data)
|
return About(**data)
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
annotated-types==0.6.0
|
|
||||||
anyio==4.2.0
|
|
||||||
black==23.12.1
|
|
||||||
certifi==2023.11.17
|
|
||||||
cffi==1.16.0
|
|
||||||
charset-normalizer==3.3.2
|
|
||||||
click==8.1.7
|
|
||||||
cryptography==41.0.7
|
|
||||||
dotted-notation==0.11.0
|
|
||||||
fastapi==0.108.0
|
|
||||||
h11==0.14.0
|
|
||||||
httpcore==1.0.2
|
|
||||||
httptools==0.6.1
|
|
||||||
httpx==0.26.0
|
|
||||||
idna==3.6
|
|
||||||
iniconfig==2.0.0
|
|
||||||
markdown-it-py==3.0.0
|
|
||||||
mdurl==0.1.2
|
|
||||||
mypy-extensions==1.0.0
|
|
||||||
mysql-connector-python==8.2.0
|
|
||||||
packaging==23.2
|
|
||||||
pathspec==0.12.1
|
|
||||||
platformdirs==4.1.0
|
|
||||||
pluggy==1.3.0
|
|
||||||
protobuf==4.21.12
|
|
||||||
pycparser==2.21
|
|
||||||
pydantic==2.5.3
|
|
||||||
pydantic-settings==2.1.0
|
|
||||||
pydantic_core==2.14.6
|
|
||||||
Pygments==2.17.2
|
|
||||||
PyJWT==2.8.0
|
|
||||||
pyparsing==3.1.1
|
|
||||||
pytest==7.4.4
|
|
||||||
python-dotenv==1.0.0
|
|
||||||
PyYAML==6.0.1
|
|
||||||
requests==2.31.0
|
|
||||||
rich==13.7.0
|
|
||||||
rich-click==1.7.2
|
|
||||||
sniffio==1.3.0
|
|
||||||
starlette==0.32.0.post1
|
|
||||||
tomlkit==0.12.3
|
|
||||||
typing_extensions==4.9.0
|
|
||||||
urllib3==2.1.0
|
|
||||||
uvicorn==0.25.0
|
|
||||||
uvloop==0.19.0
|
|
||||||
watchfiles==0.21.0
|
|
||||||
websockets==12.0
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
from fastapi.testclient import TestClient
|
|
||||||
|
|
||||||
from helpers import get_token
|
|
||||||
from main import app
|
|
||||||
|
|
||||||
client = TestClient(app)
|
|
||||||
token = get_token()
|
|
||||||
|
|
||||||
|
|
||||||
def test_root():
|
|
||||||
response = client.get("/")
|
|
||||||
assert response.status_code == 200
|
|
||||||
body: dict[str, str] = response.json()
|
|
||||||
welcome = body["welcome"]
|
|
||||||
version = body["version"]
|
|
||||||
major, minor, patch = version.split(".")
|
|
||||||
routes = body["routes"]
|
|
||||||
assert welcome == "backend api for lucasjensen.me"
|
|
||||||
for v in [major, minor, patch]:
|
|
||||||
assert v.isnumeric()
|
|
||||||
assert len(routes) >= 3
|
|
||||||
|
|
||||||
|
|
||||||
def test_about():
|
|
||||||
response = client.get("/about")
|
|
||||||
assert response.status_code == 200
|
|
||||||
body = response.json()
|
|
||||||
vals = ["name", "email", "bio", "github"]
|
|
||||||
assert all([k in body for k in vals])
|
|
||||||
|
|
||||||
|
|
||||||
def test_projects():
|
|
||||||
response = client.get("/projects")
|
|
||||||
assert response.status_code == 200
|
|
||||||
body = response.json()
|
|
||||||
assert len(body) > 0
|
|
||||||
vals = ["id", "name", "description"] # remaining vals are optional
|
|
||||||
assert all([k in body[0] for k in vals])
|
|
||||||
|
|
||||||
|
|
||||||
def test_project():
|
|
||||||
response = client.get("/projects/1")
|
|
||||||
assert response.status_code == 200
|
|
||||||
body = response.json()
|
|
||||||
vals = ["id", "name", "description"] # remaining vals are optional
|
|
||||||
assert all([k in body for k in vals])
|
|
||||||
|
|
||||||
|
|
||||||
def test_post_projects():
|
|
||||||
p_id = post_project()
|
|
||||||
|
|
||||||
client.delete(
|
|
||||||
f"/projects/{p_id}",
|
|
||||||
headers={"Authorization": f"Bearer {token}"},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def delete_project(p_id: int):
|
|
||||||
response = client.delete(
|
|
||||||
f"/projects/{p_id}",
|
|
||||||
headers={"Authorization": f"Bearer {token}"},
|
|
||||||
)
|
|
||||||
assert response.status_code == 204
|
|
||||||
|
|
||||||
response = client.get("/projects")
|
|
||||||
assert response.status_code == 200
|
|
||||||
body = response.json()
|
|
||||||
assert not any([p.get("id") == p_id for p in body])
|
|
||||||
|
|
||||||
|
|
||||||
def post_project() -> int:
|
|
||||||
project = {
|
|
||||||
"name": "test project",
|
|
||||||
"description": "test description",
|
|
||||||
"source": "github.com/test",
|
|
||||||
"live": "test.com",
|
|
||||||
"is_self_hosted": False,
|
|
||||||
}
|
|
||||||
response = client.post(
|
|
||||||
"/projects",
|
|
||||||
json=project,
|
|
||||||
headers={"Authorization": f"Bearer {token}"},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.status_code == 201
|
|
||||||
p_id = int(response.json()["id"])
|
|
||||||
assert isinstance(p_id, int)
|
|
||||||
|
|
||||||
response = client.get("/projects")
|
|
||||||
assert response.status_code == 200
|
|
||||||
body = response.json()
|
|
||||||
assert any([p.get("id") == p_id for p in body])
|
|
||||||
|
|
||||||
response = client.get(f"/projects/{p_id}")
|
|
||||||
assert response.status_code == 200
|
|
||||||
body = response.json()
|
|
||||||
|
|
||||||
assert body["name"] == project["name"]
|
|
||||||
assert body["description"] == project["description"]
|
|
||||||
assert body["source"] == project["source"]
|
|
||||||
assert body["live"] == project["live"]
|
|
||||||
assert body["id"] == p_id
|
|
||||||
|
|
||||||
return p_id
|
|
||||||
|
|
||||||
|
|
||||||
def test_delete_project():
|
|
||||||
p_id = post_project()
|
|
||||||
all_projects = client.get("/projects").json()
|
|
||||||
assert any([p.get("id") == p_id for p in all_projects])
|
|
||||||
delete_project(p_id)
|
|
||||||
all_projects = client.get("/projects").json()
|
|
||||||
assert not any([p.get("id") == p_id for p in all_projects])
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_static_file():
|
|
||||||
response = client.get("/static/resume.pdf")
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.headers["Content-Type"] == "application/pdf"
|
|
||||||
|
|
||||||
response = client.get("/static/favicon.png")
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert response.headers["Content-Type"] == "image/png"
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
import jwt
|
|
||||||
from fastapi import Depends, HTTPException, status
|
|
||||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer, SecurityScopes
|
|
||||||
|
|
||||||
from config import get_settings
|
|
||||||
|
|
||||||
|
|
||||||
class UnauthorizedException(HTTPException):
|
|
||||||
def __init__(self, detail: str, **kwargs):
|
|
||||||
"""Returns HTTP 403"""
|
|
||||||
super().__init__(status.HTTP_403_FORBIDDEN, detail=detail)
|
|
||||||
|
|
||||||
class UnauthenticatedException(HTTPException):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Requires authentication",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UnauthenticatedException(HTTPException):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Requires authentication"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class VerifyToken:
|
|
||||||
"""Does all the token verification using PyJWT"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.config = get_settings()
|
|
||||||
|
|
||||||
# This gets the JWKS from a given URL and does processing so you can
|
|
||||||
# use any of the keys available
|
|
||||||
jwks_url = f"https://{self.config.auth0_domain}/.well-known/jwks.json"
|
|
||||||
self.jwks_client = jwt.PyJWKClient(jwks_url)
|
|
||||||
|
|
||||||
async def verify(
|
|
||||||
self,
|
|
||||||
security_scopes: SecurityScopes,
|
|
||||||
token: Optional[HTTPAuthorizationCredentials] = Depends(HTTPBearer()),
|
|
||||||
):
|
|
||||||
if token is None:
|
|
||||||
raise UnauthenticatedException
|
|
||||||
|
|
||||||
# This gets the 'kid' from the passed token
|
|
||||||
try:
|
|
||||||
signing_key = self.jwks_client.get_signing_key_from_jwt(
|
|
||||||
token.credentials
|
|
||||||
).key
|
|
||||||
except jwt.exceptions.PyJWKClientError as error:
|
|
||||||
raise UnauthorizedException(str(error))
|
|
||||||
except jwt.exceptions.DecodeError as error:
|
|
||||||
raise UnauthorizedException(str(error))
|
|
||||||
|
|
||||||
try:
|
|
||||||
payload = jwt.decode(
|
|
||||||
token.credentials,
|
|
||||||
signing_key,
|
|
||||||
algorithms=self.config.auth0_algorithms, # type: ignore
|
|
||||||
audience=self.config.auth0_api_audience,
|
|
||||||
issuer=self.config.auth0_issuer,
|
|
||||||
)
|
|
||||||
except Exception as error:
|
|
||||||
raise UnauthorizedException(str(error))
|
|
||||||
|
|
||||||
return payload
|
|
||||||
Reference in New Issue
Block a user