initialize backend with FastAPI and Docker setup
This commit is contained in:
1 parent
5d724e37ab
commit
5ff02c0ff5
24 files changed
+419
-242
No files matched your search
@@ -0,0 +1,7 @@
|
|||||||
|
DB_HOST=
|
||||||
|
DB_PORT=3306
|
||||||
|
|
||||||
|
DB_NAME=
|
||||||
|
|
||||||
|
DB_USER=
|
||||||
|
DB_PASSWORD=
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY requirements.txt .
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from sqlalchemy.orm import declarative_base
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import os
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
DATABASE_URL = (
|
||||||
|
f"mysql+pymysql://"
|
||||||
|
f"{os.getenv('DB_USER')}:"
|
||||||
|
f"{os.getenv('DB_PASSWORD')}@"
|
||||||
|
f"{os.getenv('DB_HOST')}:"
|
||||||
|
f"{os.getenv('DB_PORT')}/"
|
||||||
|
f"{os.getenv('DB_NAME')}"
|
||||||
|
)
|
||||||
|
|
||||||
|
engine = create_engine(
|
||||||
|
DATABASE_URL,
|
||||||
|
pool_pre_ping=True
|
||||||
|
)
|
||||||
|
|
||||||
|
SessionLocal = sessionmaker(
|
||||||
|
autocommit=False,
|
||||||
|
autoflush=False,
|
||||||
|
bind=engine
|
||||||
|
)
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
|
def get_db():
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
yield db
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
from app.routers import projects, experiences, skills
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
app.include_router(projects.router)
|
||||||
|
app.include_router(experiences.router)
|
||||||
|
app.include_router(skills.router)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
from sqlalchemy import (
|
||||||
|
Column,
|
||||||
|
Integer,
|
||||||
|
String
|
||||||
|
)
|
||||||
|
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
from app.database import Base
|
||||||
|
|
||||||
|
|
||||||
|
class Experience(Base):
|
||||||
|
__tablename__ = "experiences"
|
||||||
|
|
||||||
|
id = Column(
|
||||||
|
Integer,
|
||||||
|
primary_key=True,
|
||||||
|
index=True
|
||||||
|
)
|
||||||
|
|
||||||
|
company = Column(
|
||||||
|
String(255),
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
location = Column(
|
||||||
|
String(255),
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
duration = Column(
|
||||||
|
String(255),
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
role = Column(
|
||||||
|
String(255),
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
tasks = relationship(
|
||||||
|
"ExperienceTask",
|
||||||
|
back_populates="experience",
|
||||||
|
cascade="all, delete-orphan"
|
||||||
|
)
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
from sqlalchemy import (
|
||||||
|
Column,
|
||||||
|
Integer,
|
||||||
|
String,
|
||||||
|
ForeignKey
|
||||||
|
)
|
||||||
|
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
from app.database import Base
|
||||||
|
|
||||||
|
|
||||||
|
class ExperienceTask(Base):
|
||||||
|
__tablename__ = "experience_tasks"
|
||||||
|
|
||||||
|
id = Column(
|
||||||
|
Integer,
|
||||||
|
primary_key=True,
|
||||||
|
index=True
|
||||||
|
)
|
||||||
|
|
||||||
|
description = Column(
|
||||||
|
String(255),
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
experience_id = Column(
|
||||||
|
Integer,
|
||||||
|
ForeignKey(
|
||||||
|
"experiences.id",
|
||||||
|
ondelete="CASCADE"
|
||||||
|
),
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
experience = relationship(
|
||||||
|
"Experience",
|
||||||
|
back_populates="tasks"
|
||||||
|
)
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
from sqlalchemy import (
|
||||||
|
Column,
|
||||||
|
Integer,
|
||||||
|
String,
|
||||||
|
Text,
|
||||||
|
Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
from app.database import Base
|
||||||
|
from app.models.project_skill import project_skills
|
||||||
|
|
||||||
|
|
||||||
|
class Project(Base):
|
||||||
|
__tablename__ = "projects"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
|
||||||
|
title = Column(
|
||||||
|
String(255),
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
nb_image = Column(
|
||||||
|
Integer,
|
||||||
|
default=1
|
||||||
|
)
|
||||||
|
|
||||||
|
short_description = Column(
|
||||||
|
Text,
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
image_name = Column(
|
||||||
|
String(255),
|
||||||
|
nullable=False,
|
||||||
|
default="empty"
|
||||||
|
)
|
||||||
|
|
||||||
|
school = Column(
|
||||||
|
Boolean,
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
long_description = Column(
|
||||||
|
Text,
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
repo_link = Column(
|
||||||
|
String(255),
|
||||||
|
nullable=True
|
||||||
|
)
|
||||||
|
|
||||||
|
beginning_year = Column(
|
||||||
|
Integer,
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
end_year = Column(
|
||||||
|
Integer,
|
||||||
|
nullable=True
|
||||||
|
)
|
||||||
|
|
||||||
|
skills = relationship(
|
||||||
|
"Skill",
|
||||||
|
secondary=project_skills,
|
||||||
|
back_populates="projects"
|
||||||
|
)
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
from sqlalchemy import Table, Column, Integer, ForeignKey
|
||||||
|
|
||||||
|
from app.database import Base
|
||||||
|
|
||||||
|
project_skills = Table(
|
||||||
|
"project_skills",
|
||||||
|
Base.metadata,
|
||||||
|
|
||||||
|
Column(
|
||||||
|
"project_id",
|
||||||
|
Integer,
|
||||||
|
ForeignKey("projects.id", ondelete="CASCADE"),
|
||||||
|
primary_key=True
|
||||||
|
),
|
||||||
|
|
||||||
|
Column(
|
||||||
|
"skill_id",
|
||||||
|
Integer,
|
||||||
|
ForeignKey("skills.id", ondelete="CASCADE"),
|
||||||
|
primary_key=True
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
from sqlalchemy import Column, Integer, String
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
from app.database import Base
|
||||||
|
from app.models.project_skill import project_skills
|
||||||
|
|
||||||
|
|
||||||
|
class Skill(Base):
|
||||||
|
__tablename__ = "skills"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
|
||||||
|
name = Column(
|
||||||
|
String(255),
|
||||||
|
nullable=False,
|
||||||
|
unique=True
|
||||||
|
)
|
||||||
|
|
||||||
|
type = Column(
|
||||||
|
String(255),
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
projects = relationship(
|
||||||
|
"Project",
|
||||||
|
secondary=project_skills,
|
||||||
|
back_populates="skills"
|
||||||
|
)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
from sqlalchemy.orm import Session, joinedload
|
||||||
|
|
||||||
|
from app.database import get_db
|
||||||
|
from app.models.experience import Experience
|
||||||
|
from app.schemas.experience import ExperienceResponse
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/experiences", tags=["Experiences"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=list[ExperienceResponse])
|
||||||
|
def get_experiences(db: Session = Depends(get_db)):
|
||||||
|
return (
|
||||||
|
db.query(Experience)
|
||||||
|
.options(joinedload(Experience.tasks))
|
||||||
|
.all()
|
||||||
|
)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
from sqlalchemy.orm import Session, joinedload
|
||||||
|
|
||||||
|
from app.database import get_db
|
||||||
|
from app.models.project import Project
|
||||||
|
from app.schemas.project import ProjectCard, ProjectPage
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/projects", tags=["Projects"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=list[ProjectCard])
|
||||||
|
def get_projects(db: Session = Depends(get_db)):
|
||||||
|
return (
|
||||||
|
db.query(Project)
|
||||||
|
.options(joinedload(Project.skills))
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{project_id}", response_model=ProjectPage)
|
||||||
|
def get_project(project_id: int, db: Session = Depends(get_db)):
|
||||||
|
project = (
|
||||||
|
db.query(Project)
|
||||||
|
.options(joinedload(Project.skills))
|
||||||
|
.filter(Project.id == project_id)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
|
if not project:
|
||||||
|
raise HTTPException(status_code=404, detail="Project not found")
|
||||||
|
|
||||||
|
return project
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from app.database import get_db
|
||||||
|
from app.models.skill import Skill
|
||||||
|
from app.schemas.skill import SkillResponse
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/skills", tags=["Skills"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=list[SkillResponse])
|
||||||
|
def get_skills(db: Session = Depends(get_db)):
|
||||||
|
return db.query(Skill).all()
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
class ExperienceTaskResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
description: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
class ExperienceResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
company: str
|
||||||
|
location: str
|
||||||
|
duration: str
|
||||||
|
role: str
|
||||||
|
tasks: list[ExperienceTaskResponse]
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = Trues
|
||||||
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
from app.schemas.skill import ProjectSkill
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectCard(BaseModel):
|
||||||
|
id: int
|
||||||
|
title: str
|
||||||
|
short_description: str
|
||||||
|
image_name: str
|
||||||
|
school: bool
|
||||||
|
beginning_year: int
|
||||||
|
end_year: int | None
|
||||||
|
skills: list[ProjectSkill]
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectPage(BaseModel):
|
||||||
|
id: int
|
||||||
|
title: str
|
||||||
|
short_description: str
|
||||||
|
long_description: str
|
||||||
|
repo_link: str | None
|
||||||
|
nb_image: int
|
||||||
|
image_name: str
|
||||||
|
school: bool
|
||||||
|
beginning_year: int
|
||||||
|
end_year: int | None
|
||||||
|
skills: list[ProjectSkill]
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
class SkillResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
type: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectSkill(BaseModel):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
-20
@@ -1,20 +0,0 @@
|
|||||||
# database.py (sans générateur)
|
|
||||||
import os
|
|
||||||
import MySQLdb
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
|
|
||||||
load_dotenv()
|
|
||||||
|
|
||||||
def get_db_connection():
|
|
||||||
try:
|
|
||||||
conn = MySQLdb.connect(
|
|
||||||
user=os.getenv("DB_USER"),
|
|
||||||
passwd=os.getenv("DB_PASS"),
|
|
||||||
host=os.getenv("DB_HOST"),
|
|
||||||
db=os.getenv("DB_NAME"),
|
|
||||||
charset='utf8mb4'
|
|
||||||
)
|
|
||||||
return conn
|
|
||||||
except Exception as e:
|
|
||||||
print(f"ERREUR CRITIQUE DE CONNEXION DB : {e}")
|
|
||||||
return None
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
backend:
|
||||||
|
build: .
|
||||||
|
container_name: portfolio-backend
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import os
|
|
||||||
from fastapi import FastAPI, HTTPException
|
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
|
||||||
from routers import skills, experiences, experienceTasks, shortProjects, longProjects
|
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
origins = [
|
|
||||||
"http://localhost",
|
|
||||||
"http://localhost:5173",
|
|
||||||
]
|
|
||||||
app.add_middleware(
|
|
||||||
CORSMiddleware,
|
|
||||||
allow_origins=origins,
|
|
||||||
allow_credentials=True,
|
|
||||||
allow_methods=["*"],
|
|
||||||
allow_headers=["*"],
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.get("/")
|
|
||||||
def read_root():
|
|
||||||
return {"status": "API Portfolio fonctionnelle !"}
|
|
||||||
|
|
||||||
app.include_router(
|
|
||||||
skills.router,
|
|
||||||
prefix="/api/skills",
|
|
||||||
tags=["Skills"],
|
|
||||||
)
|
|
||||||
app.include_router(
|
|
||||||
experiences.router,
|
|
||||||
prefix="/api/experiences",
|
|
||||||
tags=["Experiences"]
|
|
||||||
)
|
|
||||||
app.include_router(
|
|
||||||
experienceTasks.router,
|
|
||||||
prefix="/api/experienceTasks",
|
|
||||||
tags=["ExperienceTasks"]
|
|
||||||
)
|
|
||||||
app.include_router(
|
|
||||||
shortProjects.router,
|
|
||||||
prefix="/api/shortProjects",
|
|
||||||
tags=["ShortProjects"]
|
|
||||||
)
|
|
||||||
app.include_router(
|
|
||||||
longProjects.router,
|
|
||||||
prefix="/api/longProjects",
|
|
||||||
tags=["LongProjects"]
|
|
||||||
)
|
|
||||||
+3
-2
@@ -1,4 +1,5 @@
|
|||||||
fastapi
|
fastapi
|
||||||
uvicorn
|
uvicorn[standard]
|
||||||
mysqlclient
|
sqlalchemy
|
||||||
|
pymysql
|
||||||
python-dotenv
|
python-dotenv
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# routers/skills.py (sans générateur)
|
|
||||||
from fastapi import APIRouter, HTTPException
|
|
||||||
import MySQLdb
|
|
||||||
from database import get_db_connection
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/")
|
|
||||||
def read_experienceTasks():
|
|
||||||
conn = get_db_connection()
|
|
||||||
if not conn:
|
|
||||||
raise HTTPException(status_code=503, detail="Service de base de données indisponible.")
|
|
||||||
|
|
||||||
try:
|
|
||||||
with conn.cursor(MySQLdb.cursors.DictCursor) as cursor:
|
|
||||||
sql = "SELECT * FROM experience_tasks;"
|
|
||||||
cursor.execute(sql)
|
|
||||||
result = cursor.fetchall()
|
|
||||||
return {"data": result}
|
|
||||||
except MySQLdb.Error as e:
|
|
||||||
raise HTTPException(status_code=500, detail=f"Erreur lors de la requête: {e}")
|
|
||||||
finally:
|
|
||||||
conn.close()
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
# routers/skills.py (sans générateur)
|
|
||||||
from fastapi import APIRouter, HTTPException
|
|
||||||
import MySQLdb
|
|
||||||
from database import get_db_connection
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/")
|
|
||||||
def read_experiences():
|
|
||||||
conn = get_db_connection()
|
|
||||||
if not conn:
|
|
||||||
raise HTTPException(status_code=503, detail="Service de base de données indisponible.")
|
|
||||||
|
|
||||||
try:
|
|
||||||
with conn.cursor(MySQLdb.cursors.DictCursor) as cursor:
|
|
||||||
sql = "SELECT * FROM experiences;"
|
|
||||||
cursor.execute(sql)
|
|
||||||
result = cursor.fetchall()
|
|
||||||
return {"data": result}
|
|
||||||
except MySQLdb.Error as e:
|
|
||||||
raise HTTPException(status_code=500, detail=f"Erreur lors de la requête: {e}")
|
|
||||||
finally:
|
|
||||||
conn.close()
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
from fastapi import APIRouter, HTTPException
|
|
||||||
import MySQLdb
|
|
||||||
from database import get_db_connection
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
@router.get("/{project_id}")
|
|
||||||
def read_long_project(project_id: int):
|
|
||||||
conn = get_db_connection()
|
|
||||||
if not conn:
|
|
||||||
raise HTTPException(status_code=503, detail="Service de base de données indisponible.")
|
|
||||||
|
|
||||||
try:
|
|
||||||
with conn.cursor(MySQLdb.cursors.DictCursor) as cursor:
|
|
||||||
sql = """
|
|
||||||
SELECT
|
|
||||||
p.id,
|
|
||||||
p.title,
|
|
||||||
p.long_description,
|
|
||||||
p.short_description,
|
|
||||||
p.school,
|
|
||||||
p.repo_link,
|
|
||||||
p.nb_image,
|
|
||||||
p.image_name,
|
|
||||||
p.beginning_year,
|
|
||||||
p.end_year,
|
|
||||||
GROUP_CONCAT(DISTINCT s.name ORDER BY s.name SEPARATOR ', ') AS skills
|
|
||||||
FROM
|
|
||||||
projects p
|
|
||||||
LEFT JOIN
|
|
||||||
project_skills ps ON p.id = ps.project_id
|
|
||||||
LEFT JOIN
|
|
||||||
skills s ON ps.skill_id = s.id
|
|
||||||
WHERE p.id = %s
|
|
||||||
GROUP BY p.id;
|
|
||||||
"""
|
|
||||||
cursor.execute(sql, (project_id,))
|
|
||||||
result = cursor.fetchone()
|
|
||||||
|
|
||||||
if not result:
|
|
||||||
raise HTTPException(status_code=404, detail="Projet non trouvé.")
|
|
||||||
|
|
||||||
if result['skills']:
|
|
||||||
result['skills'] = result['skills'].split(', ')
|
|
||||||
else:
|
|
||||||
result['skills'] = []
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
except MySQLdb.Error as e:
|
|
||||||
raise HTTPException(status_code=500, detail=f"Erreur lors de la requête: {e}")
|
|
||||||
finally:
|
|
||||||
conn.close()
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
from fastapi import APIRouter, HTTPException
|
|
||||||
import MySQLdb
|
|
||||||
from database import get_db_connection
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/")
|
|
||||||
def read_shortProjects():
|
|
||||||
conn = get_db_connection()
|
|
||||||
if not conn:
|
|
||||||
raise HTTPException(status_code=503, detail="Service de base de données indisponible.")
|
|
||||||
|
|
||||||
try:
|
|
||||||
with conn.cursor(MySQLdb.cursors.DictCursor) as cursor:
|
|
||||||
sql = """
|
|
||||||
SELECT
|
|
||||||
p.id,
|
|
||||||
p.title,
|
|
||||||
p.short_description,
|
|
||||||
p.school,
|
|
||||||
p.image_name,
|
|
||||||
p.beginning_year,
|
|
||||||
p.end_year,
|
|
||||||
GROUP_CONCAT(DISTINCT s.name ORDER BY s.name SEPARATOR ', ') AS skills
|
|
||||||
FROM
|
|
||||||
projects p
|
|
||||||
LEFT JOIN
|
|
||||||
project_skills ps ON p.id = ps.project_id
|
|
||||||
LEFT JOIN
|
|
||||||
skills s ON ps.skill_id = s.id
|
|
||||||
GROUP BY
|
|
||||||
p.id;
|
|
||||||
"""
|
|
||||||
cursor.execute(sql)
|
|
||||||
result = cursor.fetchall()
|
|
||||||
|
|
||||||
for project in result:
|
|
||||||
if project['skills']:
|
|
||||||
project['skills'] = project['skills'].split(', ')
|
|
||||||
else:
|
|
||||||
project['skills'] = []
|
|
||||||
|
|
||||||
return {"data": result}
|
|
||||||
except MySQLdb.Error as e:
|
|
||||||
raise HTTPException(status_code=500, detail=f"Erreur lors de la requête: {e}")
|
|
||||||
finally:
|
|
||||||
conn.close()
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# routers/skills.py (sans générateur)
|
|
||||||
from fastapi import APIRouter, HTTPException
|
|
||||||
import MySQLdb
|
|
||||||
from database import get_db_connection
|
|
||||||
|
|
||||||
router = APIRouter()
|
|
||||||
|
|
||||||
@router.get("/")
|
|
||||||
def read_skills():
|
|
||||||
conn = get_db_connection()
|
|
||||||
if not conn:
|
|
||||||
raise HTTPException(status_code=503, detail="Service de base de données indisponible.")
|
|
||||||
|
|
||||||
try:
|
|
||||||
with conn.cursor(MySQLdb.cursors.DictCursor) as cursor:
|
|
||||||
sql = "SELECT * FROM skills;"
|
|
||||||
cursor.execute(sql)
|
|
||||||
result = cursor.fetchall()
|
|
||||||
return {"data": result}
|
|
||||||
except MySQLdb.Error as e:
|
|
||||||
raise HTTPException(status_code=500, detail=f"Erreur lors de la requête: {e}")
|
|
||||||
finally:
|
|
||||||
conn.close()
|
|
||||||
Reference in new issue
Block a user