feat: initialize FastAPI project structure with job posting routes and models

This commit is contained in:
Esa Kataja
2025-08-04 11:38:07 +03:00
parent f007284ad8
commit 6374f2f68a
11 changed files with 187 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
from .logger import logger
from .settings import settings
__all__ = ["logger", "settings"]
+9
View File
@@ -0,0 +1,9 @@
from fastapi import HTTPException, status
class NotImplementedException(HTTPException):
def __init__(self):
super().__init__(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
detail="Not implemented yet",
)
+24
View File
@@ -0,0 +1,24 @@
from loguru import logger
from sys import stdout
from .settings import settings
logger.remove()
logger.add(
stdout,
format="{time:YYYY-MM-DD at HH:mm:ss} | {file}:{line} | <level>{level}</level> | {message} | {extra}",
colorize=True,
enqueue=True,
)
logger.add(
settings.log_dir / "file.log",
format="",
serialize=True,
level="INFO",
rotation="10 MB",
retention="1 week",
enqueue=True,
compression="gz",
)
+17
View File
@@ -0,0 +1,17 @@
from pydantic_settings import BaseSettings
from pydantic import Field
from pathlib import Path
class Settings(BaseSettings):
data_dir: Path = Field(default=Path(__file__).parent.parent / "data")
@property
def log_dir(self) -> Path:
dir = self.data_dir / "logs"
if not dir.is_dir():
dir.mkdir(parents=True, exist_ok=True)
return dir
settings = Settings()
+27
View File
@@ -0,0 +1,27 @@
from fastapi import FastAPI
from contextlib import asynccontextmanager
from routes import routes
from lib import logger
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup code here
logger.info("Application startup")
yield
# Shutdown code here
logger.info("Application shutdown")
app = FastAPI(lifespan=lifespan)
for route in routes:
app.include_router(route)
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
+10
View File
@@ -0,0 +1,10 @@
from pydantic import BaseModel, Field
class JobListing(BaseModel):
title: str = Field(..., description="Job title")
company: str = Field(..., description="Company name")
location: str = Field(..., description="Location")
description: str = Field(..., description="Job description")
salary: int | None = Field(None, description="Salary")
url: str = Field(..., description="Job URL")
+36
View File
@@ -0,0 +1,36 @@
from pydantic import BaseModel, Field
from datetime import datetime
from zoneinfo import ZoneInfo
from enum import Enum
class _Type(Enum):
INFO = "info"
WARNING = "warning"
ERROR = "error"
SUCCESS = "success"
class _Message(BaseModel):
msg: str = Field(..., description="Message")
timestamp: datetime = Field(
default_factory=lambda: datetime.now(tz=ZoneInfo("UTC")),
description="Timestamp",
)
class Error(_Message):
msg_type: _Type = _Type.ERROR
class Warning(_Message):
msg_type: _Type = _Type.WARNING
class Info(_Message):
msg_type: _Type = _Type.INFO
class Success(_Message):
msg_type: _Type = _Type.SUCCESS
+6
View File
@@ -0,0 +1,6 @@
from .default import router as default_router
from .job_postings import router as job_postings_router
routes = [default_router, job_postings_router]
__all__ = ["routes"]
+14
View File
@@ -0,0 +1,14 @@
from fastapi import APIRouter
from models.message import Success
router = APIRouter(tags=["Default"])
@router.get("/")
def root():
return Success(msg="Hello World")
@router.get("/health")
def health():
return Success(msg="ok")
+31
View File
@@ -0,0 +1,31 @@
from fastapi import APIRouter
from enum import Enum
from lib.exceptions import NotImplementedException
from lib import logger
router = APIRouter(prefix="/job_postings", tags=["Job Postings"])
class PostingSource(Enum):
ALL = "all"
LINKEDIN = "linkedin"
DUUNITORI = "duunitori"
TYOMARKKINATORI = "tyomarkkinatori"
@router.get("/")
def get_job_postings(source: PostingSource = PostingSource.ALL):
match source:
case PostingSource.ALL:
logger.error(f"Job postings from {source.value} Not implemented yet")
raise NotImplementedException()
case PostingSource.LINKEDIN:
logger.error(f"Job postings from {source.value} Not implemented yet")
raise NotImplementedException()
case PostingSource.DUUNITORI:
logger.error(f"Job postings from {source.value} Not implemented yet")
raise NotImplementedException()
case PostingSource.TYOMARKKINATORI:
logger.error(f"Job postings from {source.value} Not implemented yet")
raise NotImplementedException()