Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/backend/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.jobs-api-venv/

*env/
*venv/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
Expand Down
Empty file added apps/backend/Dockerfile
Empty file.
Empty file added apps/backend/README.md
Empty file.
57 changes: 0 additions & 57 deletions apps/backend/main.py

This file was deleted.

3 changes: 2 additions & 1 deletion apps/backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ fastapi
uvicorn[standard]
SQLAlchemy
psycopg2-binary
pydantic
alembic
pydantic[email]
pydantic-settings
passlib[bcrypt]
python-dotenv
requests==2.32.1
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/run.sh
Original file line number Diff line number Diff line change
@@ -1 +1 @@
uvicorn main:app --reload
uvicorn src.main:app --reload
Empty file.
8 changes: 8 additions & 0 deletions apps/backend/src/api/deps/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from ...db.session import SessionLocal

def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
50 changes: 50 additions & 0 deletions apps/backend/src/api/deps/pagination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from fastapi import Query, HTTPException
from math import ceil
from typing import Optional
from ...core.constants import Constants


async def pagination_params(
page: Optional[int] = Query(1, ge=1, description='Page number (1-indexed)'),
per_page: Optional[int] = Query(
Constants.DEFAULT_PAGE_SIZE, ge=1, le=Constants.MAX_PAGE_SIZE, description='Items per page'
),
):
'''
Common dependency to extract pagination parameters from query string.
Example: ?page=2&per_page=20
'''
return {'page': page, 'per_page': per_page}


def paginate_query(query, pagination: dict):
'''
Apply pagination to an SQLAlchemy query using page & per_page.
'''
page = pagination.get('page', 1)
per_page = pagination.get('per_page', Constants.DEFAULT_PAGE_SIZE)

if per_page > Constants.MAX_PER_PAGE:
raise HTTPException(status_code=400, detail=f'per_page cannot exceed {Constants.MAX_PER_PAGE}')

offset = (page - 1) * per_page
return query.limit(per_page).offset(offset)


def build_paginated_response(items: list, total: int, page: int, per_page: int):
'''
Build a consistent paginated response payload with metadata.
'''
total_pages = ceil(total / per_page) if total > 0 else 1

return {
'meta': {
'total': total,
'page': page,
'per_page': per_page,
'total_pages': total_pages,
'has_next': page < total_pages,
'has_prev': page > 1,
},
'results': items,
}
Empty file.
Empty file.
39 changes: 39 additions & 0 deletions apps/backend/src/api/v1/routes/jobs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from uuid import UUID

from ....schemas import job as schemas
from ...deps.db import get_db
from ...deps.pagination import pagination_params
from ....services import job as job_service

router = APIRouter(prefix='/jobs')

# @router.post('/', response_model=schemas.JobBase)
# def create_job(job_in: schemas.JobBase, db: Session = Depends(get_db)):
# return job_service.create_job(db, job_in)

# @router.get('/{job_id}', response_model=schemas.JobBase)
# def get_job(job_id: UUID, db: Session = Depends(get_db)):
# db_job = job_service.get_job(db, job_id)
# if not db_job:
# raise HTTPException(status_code=404, detail='Job not found')
# return db_job

@router.get('/', response_model=list[schemas.JobBase])
def list_jobs(query: str | None = None, pagination: dict = Depends(pagination_params), db: Session = Depends(get_db)):
return job_service.get_jobs(db, pagination, query)

# @router.put('/{job_id}', response_model=schemas.JobBase)
# def update_job(job_id: UUID, job_update: schemas.JobBase, db: Session = Depends(get_db)):
# db_job = job_service.update_job(db, job_id, job_update)
# if not db_job:
# raise HTTPException(status_code=404, detail='Job not found')
# return db_job

# @router.delete('/{job_id}')bs
# def delete_job(job_id: UUID, db: Session = Depends(get_db)):
# success = job_service.delete_job(db, job_id)
# if not success:
# raise HTTPException(status_code=404, detail='Job not found')
# return {'detail': 'Job deleted'}
Empty file.
Empty file.
58 changes: 0 additions & 58 deletions apps/backend/src/controller.py

This file was deleted.

Empty file.
14 changes: 14 additions & 0 deletions apps/backend/src/core/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
# Database
DATABASE_URL: str
BACKEND_CORS_ORIGINS: list[str] = []
LOG_LEVEL: str = 'INFO'

class Config:
env_file = '.env'
case_sensitive = True


settings = Settings()
3 changes: 3 additions & 0 deletions apps/backend/src/core/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Constants:
DEFAULT_PAGE_SIZE: int = 10
MAX_PAGE_SIZE: int = 100
Empty file.
21 changes: 0 additions & 21 deletions apps/backend/src/database.py

This file was deleted.

9 changes: 9 additions & 0 deletions apps/backend/src/db/base_class.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from datetime import datetime
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import DateTime

class Base(DeclarativeBase):
id: Mapped[int] = mapped_column('id', primary_key=True, autoincrement=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
__abstract__ = True
17 changes: 17 additions & 0 deletions apps/backend/src/db/session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from ..core.config import settings

DATABASE_URL = settings.DATABASE_URL

engine = create_engine(
DATABASE_URL,
echo=settings.LOG_LEVEL == 'debug',
future=True
)

SessionLocal = sessionmaker(
autocommit=False,
autoflush=False,
bind=engine
)
18 changes: 18 additions & 0 deletions apps/backend/src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from fastapi import FastAPI
from .api.v1.routes import jobs
from .models import company, job, skill, user
from .db.base_class import Base
from .db.session import engine

# Ensure tables are created
Base.metadata.create_all(bind=engine)

app = FastAPI(title='JobApplica API')

api_v1_prefix = '/api/v1'

app.include_router(jobs.router, prefix=api_v1_prefix, tags=['Jobs'])
# app.include_router(users.router, prefix=api_v1_prefix, tags=['Users'])
# app.include_router(plugins.router, prefix=api_v1_prefix, tags=['Plugins'])
# app.include_router(auth.router, prefix=api_v1_prefix, tags=['Auth'])
# app.include_router(health.router, prefix=api_v1_prefix, tags=['Health'])
36 changes: 0 additions & 36 deletions apps/backend/src/models.py

This file was deleted.

20 changes: 20 additions & 0 deletions apps/backend/src/models/company.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# src/models/company.py
from sqlalchemy import String, Integer
from sqlalchemy.orm import Mapped, mapped_column, relationship

from ..db.base_class import Base

class Company(Base):
__tablename__ = 'companies'
name: Mapped[str] = mapped_column(String(100), nullable=False, unique=True)
website: Mapped[str] = mapped_column(String(255), nullable=True)
email: Mapped[str] = mapped_column(String(255), nullable=True)
location: Mapped[str] = mapped_column(String(255), nullable=False)
size: Mapped[int] = mapped_column(Integer, nullable=True)
headquarters: Mapped[str] = mapped_column(String(255), nullable=True)
founded_year: Mapped[int] = mapped_column(Integer, nullable=True)
industry: Mapped[str] = mapped_column(String(255), nullable=True)
description: Mapped[str] = mapped_column(String(500), nullable=True)

def __repr__(self):
return f"<Company id={self.id} name='{self.name}'>"
Loading
Loading