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
44 changes: 44 additions & 0 deletions .github/workflows/backend-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Backend Python Lint

# 1: Trigger only when python files are changed
on:
push:
branches: ["main"]
paths:
- "backend/**/*.py"
- ".github/workflows/backend-lint.yml"
pull_request:
branches: ["main"]
paths:
- "backend/**/*.py"
- ".github/workflows/backend-lint.yml"

jobs:
lint-backend:
runs-on: ubuntu-latest

steps:
- name: Checkout the repository code
uses: actions/checkout@v4

# 2: Pin exact python version requested
- name: Setup python 3.10.12
uses: actions/setup-python@v5
with:
python-version: "3.10.12"

- name: Install Linting tools
run: |
python -m pip install --upgrade pip
pip install black flake8

# 3: Check code formatting
- name: Formatting with Black
run: |
# --check means "dont fix it, just fail the CI if it's formatted wrong"
black . --check

# 4: Check for syntax errors and undefined names
- name: Linting with Flake8
run: |
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
42 changes: 42 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Backend Documentation

## Overview
FastAPI-based backend for The Method application. Handles resume analysis, interview preparation, job applications, and AI-powered recommendations using LLM integration.

## Setup

### Prerequisites
- Python 3.12.3+
- PostgreSQL w/ SqlAlchemy
- GROQ API key
- Google Developer key(future)

### Installation
```bash
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
```

### Formatting Check
```bash
black . --check
```

### Auto Formatting Python Code
```bash
black .
```

#### Check for errors
```bash
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
```

**Selected error codes:**
- `E9` = Syntax errors
- `F63` = Invalid use of `*` in function definition
- `F7` = Undefined name in function definition
- `F82` = Undefined name in code
12 changes: 11 additions & 1 deletion backend/database/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,12 @@
from .engine import get_db, init_db
from .models import User, UserInfo, Education, Skill, Experience, Project, Certification, Award, Resume
from .models import (
User,
UserInfo,
Education,
Skill,
Experience,
Project,
Certification,
Award,
Resume,
)
34 changes: 19 additions & 15 deletions backend/database/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,30 @@

from .models import Base

PSYCOPG_DATABASE_URL = os.getenv('DATABASE_URL').replace('postgresql://', 'postgresql+psycopg2://')
PSYCOPG_DATABASE_URL = os.getenv("DATABASE_URL").replace(
"postgresql://", "postgresql+psycopg2://"
)

engine = create_engine(PSYCOPG_DATABASE_URL)

SessionLocal = sessionmaker(bind=engine, autoflush=False)


def get_db():
"""
Returns a database session
"""
db = SessionLocal()
try:
yield db
finally:
db.close()
"""
Returns a database session
"""
db = SessionLocal()
try:
yield db
finally:
db.close()


def init_db():
"""
Initializes the database
"""
with engine.begin() as conn:
conn.execute(text('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";'))
Base.metadata.create_all(bind=engine)
"""
Initializes the database
"""
with engine.begin() as conn:
conn.execute(text('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";'))
Base.metadata.create_all(bind=engine)
145 changes: 97 additions & 48 deletions backend/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
from typing import List, Optional

from sqlalchemy import Boolean, Numeric, Date, Text, ForeignKey, func, text
from sqlalchemy.dialects.postgresql import UUID as PG_UUID, JSONB as PG_JSONB, ARRAY as PG_ARRAY, ENUM as PG_ENUM
from sqlalchemy.dialects.postgresql import (
UUID as PG_UUID,
JSONB as PG_JSONB,
ARRAY as PG_ARRAY,
ENUM as PG_ENUM,
)
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship


Expand All @@ -13,52 +18,76 @@ class Base(DeclarativeBase):


class JobType(Enum):
FULL_TIME = 'Full-time'
PART_TIME = 'Part-time'
CONTRACT = 'Contract'
INTERNSHIP = 'Internship'
FREELANCE = 'Freelance'
VOLUNTEER = 'Volunteer'
FULL_TIME = "Full-time"
PART_TIME = "Part-time"
CONTRACT = "Contract"
INTERNSHIP = "Internship"
FREELANCE = "Freelance"
VOLUNTEER = "Volunteer"


class User(Base):
__tablename__ = 'users'
__tablename__ = "users"

id: Mapped[UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, server_default=text('uuid_generate_v4()'))
id: Mapped[UUID] = mapped_column(
PG_UUID(as_uuid=True),
primary_key=True,
server_default=text("uuid_generate_v4()"),
)
email: Mapped[str] = mapped_column(Text, unique=True, nullable=False)
password_hash: Mapped[str] = mapped_column(Text, nullable=False)
created_at: Mapped[datetime] = mapped_column(server_default=func.now())
last_email_update: Mapped[datetime] = mapped_column(server_default=func.now())
last_password_update: Mapped[datetime] = mapped_column(server_default=func.now())

user_info: Mapped['UserInfo'] = relationship(back_populates='user', uselist=False, cascade='all, delete-orphan')
resumes: Mapped[List['Resume']] = relationship(back_populates='user', cascade='all, delete-orphan')
user_info: Mapped["UserInfo"] = relationship(
back_populates="user", uselist=False, cascade="all, delete-orphan"
)
resumes: Mapped[List["Resume"]] = relationship(
back_populates="user", cascade="all, delete-orphan"
)


class UserInfo(Base):
__tablename__ = 'user_infos'
__tablename__ = "user_infos"

id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
user_id: Mapped[UUID] = mapped_column(ForeignKey('users.id', ondelete='CASCADE'), unique=True, nullable=False)
user_id: Mapped[UUID] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), unique=True, nullable=False
)
name: Mapped[Optional[str]] = mapped_column(Text)
phone_number: Mapped[Optional[str]] = mapped_column(Text)
us_citizen: Mapped[bool] = mapped_column(Boolean, server_default=text('false'))
links: Mapped[list] = mapped_column(PG_JSONB, server_default=text('\'[]\'::jsonb'))

user: Mapped['User'] = relationship(back_populates='user_info')
educations: Mapped[List['Education']] = relationship(back_populates='user_info', cascade='all, delete-orphan')
skills: Mapped[List['Skill']] = relationship(back_populates='user_info', cascade='all, delete-orphan')
experiences: Mapped[List['Experience']] = relationship(back_populates='user_info', cascade='all, delete-orphan')
projects: Mapped[List['Project']] = relationship(back_populates='user_info', cascade='all, delete-orphan')
certifications: Mapped[List['Certification']] = relationship(back_populates='user_info', cascade='all, delete-orphan')
awards: Mapped[List['Award']] = relationship(back_populates='user_info', cascade='all, delete-orphan')
us_citizen: Mapped[bool] = mapped_column(Boolean, server_default=text("false"))
links: Mapped[list] = mapped_column(PG_JSONB, server_default=text("'[]'::jsonb"))

user: Mapped["User"] = relationship(back_populates="user_info")
educations: Mapped[List["Education"]] = relationship(
back_populates="user_info", cascade="all, delete-orphan"
)
skills: Mapped[List["Skill"]] = relationship(
back_populates="user_info", cascade="all, delete-orphan"
)
experiences: Mapped[List["Experience"]] = relationship(
back_populates="user_info", cascade="all, delete-orphan"
)
projects: Mapped[List["Project"]] = relationship(
back_populates="user_info", cascade="all, delete-orphan"
)
certifications: Mapped[List["Certification"]] = relationship(
back_populates="user_info", cascade="all, delete-orphan"
)
awards: Mapped[List["Award"]] = relationship(
back_populates="user_info", cascade="all, delete-orphan"
)


class Education(Base):
__tablename__ = 'educations'
__tablename__ = "educations"

id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
user_info_id: Mapped[int] = mapped_column(ForeignKey('user_infos.id', ondelete='CASCADE'), nullable=False)
user_info_id: Mapped[int] = mapped_column(
ForeignKey("user_infos.id", ondelete="CASCADE"), nullable=False
)
school: Mapped[str] = mapped_column(Text, nullable=False)
major: Mapped[str] = mapped_column(Text, nullable=False)
gpa: Mapped[float] = mapped_column(Numeric(3, 2))
Expand All @@ -67,83 +96,103 @@ class Education(Base):
activities: Mapped[Optional[list[str]]] = mapped_column(PG_ARRAY(Text))
coursework: Mapped[Optional[list[str]]] = mapped_column(PG_ARRAY(Text))

user_info: Mapped['UserInfo'] = relationship(back_populates='educations')
user_info: Mapped["UserInfo"] = relationship(back_populates="educations")


class Skill(Base):
__tablename__ = 'skills'
__tablename__ = "skills"

id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
user_info_id: Mapped[int] = mapped_column(ForeignKey('user_infos.id', ondelete='CASCADE'), nullable=False)
user_info_id: Mapped[int] = mapped_column(
ForeignKey("user_infos.id", ondelete="CASCADE"), nullable=False
)
category_name: Mapped[str] = mapped_column(Text, nullable=False)
skill_list: Mapped[list[str]] = mapped_column(PG_ARRAY(Text), nullable=False)

user_info: Mapped['UserInfo'] = relationship(back_populates='skills')
user_info: Mapped["UserInfo"] = relationship(back_populates="skills")


class Experience(Base):
__tablename__ = 'experiences'
__tablename__ = "experiences"

id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
user_info_id: Mapped[int] = mapped_column(ForeignKey('user_infos.id', ondelete='CASCADE'), nullable=False)
user_info_id: Mapped[int] = mapped_column(
ForeignKey("user_infos.id", ondelete="CASCADE"), nullable=False
)
title: Mapped[str] = mapped_column(Text, nullable=False)
company: Mapped[str] = mapped_column(Text, nullable=False)
city: Mapped[str] = mapped_column(Text, nullable=False)
state: Mapped[str] = mapped_column(Text, nullable=False)
job_type: Mapped[JobType] = mapped_column(PG_ENUM(JobType, name='job_type_enum'), nullable=False)
job_type: Mapped[JobType] = mapped_column(
PG_ENUM(JobType, name="job_type_enum"), nullable=False
)
start_date: Mapped[date] = mapped_column(Date, nullable=False)
end_date: Mapped[Optional[date]] = mapped_column(Date)
description: Mapped[Optional[str]] = mapped_column(Text)

user_info: Mapped['UserInfo'] = relationship(back_populates='experiences')
user_info: Mapped["UserInfo"] = relationship(back_populates="experiences")


class Project(Base):
__tablename__ = 'projects'
__tablename__ = "projects"

id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
user_info_id: Mapped[int] = mapped_column(ForeignKey('user_infos.id', ondelete='CASCADE'), nullable=False)
user_info_id: Mapped[int] = mapped_column(
ForeignKey("user_infos.id", ondelete="CASCADE"), nullable=False
)
name: Mapped[str] = mapped_column(Text, nullable=False)
start_date: Mapped[date] = mapped_column(Date, nullable=False)
end_date: Mapped[Optional[date]] = mapped_column(Date)
description: Mapped[Optional[str]] = mapped_column(Text)

user_info: Mapped['UserInfo'] = relationship(back_populates='projects')
user_info: Mapped["UserInfo"] = relationship(back_populates="projects")


class Certification(Base):
__tablename__ = 'certifications'
__tablename__ = "certifications"

id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
user_info_id: Mapped[int] = mapped_column(ForeignKey('user_infos.id', ondelete='CASCADE'), nullable=False)
user_info_id: Mapped[int] = mapped_column(
ForeignKey("user_infos.id", ondelete="CASCADE"), nullable=False
)
name: Mapped[str] = mapped_column(Text, nullable=False)
company: Mapped[str] = mapped_column(Text, nullable=False)
date_issued: Mapped[date] = mapped_column(Date, nullable=False)

user_info: Mapped['UserInfo'] = relationship(back_populates='certifications')
user_info: Mapped["UserInfo"] = relationship(back_populates="certifications")


class Award(Base):
__tablename__ = 'awards'
__tablename__ = "awards"

id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
user_info_id: Mapped[int] = mapped_column(ForeignKey('user_infos.id', ondelete='CASCADE'), nullable=False)
user_info_id: Mapped[int] = mapped_column(
ForeignKey("user_infos.id", ondelete="CASCADE"), nullable=False
)
name: Mapped[str] = mapped_column(Text, nullable=False)
company: Mapped[str] = mapped_column(Text, nullable=False)
city: Mapped[Optional[str]] = mapped_column(Text)
state: Mapped[Optional[str]] = mapped_column(Text)
date_received: Mapped[date] = mapped_column(Date, nullable=False)

user_info: Mapped['UserInfo'] = relationship(back_populates='awards')
user_info: Mapped["UserInfo"] = relationship(back_populates="awards")


class Resume(Base):
__tablename__ = 'resumes'

id: Mapped[UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True, server_default=text('uuid_generate_v4()'))
user_id: Mapped[UUID] = mapped_column(ForeignKey('users.id', ondelete='CASCADE'), unique=True, nullable=False)
__tablename__ = "resumes"

id: Mapped[UUID] = mapped_column(
PG_UUID(as_uuid=True),
primary_key=True,
server_default=text("uuid_generate_v4()"),
)
user_id: Mapped[UUID] = mapped_column(
ForeignKey("users.id", ondelete="CASCADE"), unique=True, nullable=False
)
resume_data: Mapped[str] = mapped_column(Text, nullable=False)
created_at: Mapped[datetime] = mapped_column(server_default=func.now())
last_updated: Mapped[datetime] = mapped_column(server_default=func.now(), onupdate=func.now())
last_updated: Mapped[datetime] = mapped_column(
server_default=func.now(), onupdate=func.now()
)

user: Mapped['User'] = relationship(back_populates='resume')
user: Mapped["User"] = relationship(back_populates="resume")
Loading
Loading