Versión 3 Backend - Endpoints finales corregidos

This commit is contained in:
Mireya Cueto Garrido
2026-04-27 13:39:32 +02:00
parent a286c2e3ae
commit 96f01c0126
4343 changed files with 1046097 additions and 465 deletions
+3
View File
@@ -0,0 +1,3 @@
from sqlalchemy.orm import declarative_base
Base = declarative_base()
+41 -38
View File
@@ -1,60 +1,63 @@
from sqlalchemy import Column, String, Boolean, Integer, DateTime, Text, ForeignKey
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.sql import func
from sqlalchemy import Column, String, Integer, Boolean, DateTime, ForeignKey
from sqlalchemy.dialects.postgresql import UUID, JSONB
from sqlalchemy.orm import relationship
from .session import Base
import uuid
from datetime import datetime
from app.db.session import Base
class Researcher(Base):
__tablename__ = "researchers"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
orcid_id = Column(String(19), unique=True, nullable=False)
name = Column(Text)
orcid_id = Column(String, unique=True, index=True, nullable=False)
name = Column(String, nullable=True)
authenticated = Column(Boolean, default=False)
access_token = Column(Text, nullable=True)
last_sync_at = Column(DateTime(timezone=True), server_default=func.now())
last_sync_at = Column(DateTime, nullable=True)
publications = relationship(
"Publication",
back_populates="researcher",
cascade="all, delete-orphan"
)
sync_jobs = relationship(
"SyncJob",
back_populates="researcher",
cascade="all, delete-orphan"
)
publications = relationship("Publication", back_populates="researcher", cascade="all, delete-orphan")
class Publication(Base):
__tablename__ = "publications"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
researcher_id = Column(UUID(as_uuid=True), ForeignKey("researchers.id"))
put_code = Column(Integer)
title = Column(Text)
journal = Column(Text)
doi = Column(Text)
pub_year = Column(Integer)
type = Column(Text)
hash_fingerprint = Column(Text)
last_modified = Column(DateTime(timezone=True))
researcher_id = Column(UUID(as_uuid=True), ForeignKey("researchers.id"), nullable=False)
researcher = relationship("Researcher", back_populates="publications")
# ORCID core
put_code = Column(Integer, index=True, nullable=False)
title = Column(String, nullable=True)
subtitle = Column(String, nullable=True)
type = Column(String, nullable=True)
class SyncJob(Base):
__tablename__ = "sync_jobs"
# Journal / container
journal = Column(String, nullable=True)
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
researcher_id = Column(UUID(as_uuid=True), ForeignKey("researchers.id"))
status = Column(String(20))
new_records = Column(Integer, default=0)
updated_records = Column(Integer, default=0)
started_at = Column(DateTime(timezone=True), server_default=func.now())
finished_at = Column(DateTime(timezone=True))
# Dates
pub_year = Column(Integer, nullable=True)
pub_month = Column(Integer, nullable=True)
pub_day = Column(Integer, nullable=True)
researcher = relationship("Researcher", back_populates="sync_jobs")
# Identifiers / links
doi = Column(String, nullable=True)
url = Column(String, nullable=True)
# Description / citation
short_description = Column(String, nullable=True)
citation_type = Column(String, nullable=True)
citation_value = Column(String, nullable=True)
# Language / country
language_code = Column(String, nullable=True)
country = Column(String, nullable=True)
# Extra structured data
external_ids = Column(JSONB, nullable=True) # lista de external-id normalizados
contributors = Column(JSONB, nullable=True) # lista de autores/roles
# Tu campo existente
hash_fingerprint = Column(String, nullable=True)
last_modified = Column(DateTime, nullable=True, default=None)
@@ -0,0 +1,66 @@
from sqlalchemy.orm import Session
from app.db.models import Publication
class PublicationRepository:
@staticmethod
def get_by_put_code(db: Session, researcher_id: str, put_code: int):
"""
Devuelve una publicación existente por put_code (único en ORCID).
"""
return (
db.query(Publication)
.filter(
Publication.researcher_id == researcher_id,
Publication.put_code == put_code
)
.first()
)
@staticmethod
def create(db: Session, researcher_id: str, data: dict):
"""
Crea una nueva publicación normalizada.
"""
pub = Publication(
researcher_id=researcher_id,
put_code=data["put_code"],
title=data["title"],
journal=data["journal"],
doi=data["doi"],
pub_year=data["pub_year"],
type=data["type"],
hash_fingerprint=data["hash_fingerprint"]
)
db.add(pub)
db.commit()
db.refresh(pub)
return pub
@staticmethod
def update(db: Session, publication: Publication, data: dict):
"""
Actualiza una publicación existente si ORCID ha cambiado algo.
"""
publication.title = data["title"]
publication.journal = data["journal"]
publication.doi = data["doi"]
publication.pub_year = data["pub_year"]
publication.type = data["type"]
publication.hash_fingerprint = data["hash_fingerprint"]
db.commit()
db.refresh(publication)
return publication
@staticmethod
def list_by_researcher(db: Session, researcher_id: str):
"""
Lista todas las publicaciones de un investigador.
"""
return (
db.query(Publication)
.filter(Publication.researcher_id == researcher_id)
.order_by(Publication.pub_year.desc().nullslast())
.all()
)
@@ -0,0 +1,25 @@
from sqlalchemy.orm import Session
from app.db.models import Researcher
from sqlalchemy.sql import func
class ResearcherRepository:
@staticmethod
def get_by_orcid(db: Session, orcid_id: str):
return db.query(Researcher).filter(Researcher.orcid_id == orcid_id).first()
@staticmethod
def create(db: Session, orcid_id: str, name: str = None):
researcher = Researcher(orcid_id=orcid_id, name=name)
db.add(researcher)
db.commit()
db.refresh(researcher)
return researcher
@staticmethod
def update_last_sync(db: Session, researcher: Researcher):
researcher.last_sync_at = func.now()
db.commit()
db.refresh(researcher)
return researcher
@@ -0,0 +1,28 @@
from sqlalchemy.orm import Session
from app.db.models import SyncJob
from sqlalchemy.sql import func
class SyncJobRepository:
@staticmethod
def start_job(db: Session, researcher_id: str):
job = SyncJob(
researcher_id=researcher_id,
status="running",
started_at=func.now()
)
db.add(job)
db.commit()
db.refresh(job)
return job
@staticmethod
def finish_job(db: Session, job: SyncJob, new_records: int, updated_records: int):
job.status = "finished"
job.new_records = new_records
job.updated_records = updated_records
job.finished_at = func.now()
db.commit()
db.refresh(job)
return job
+18
View File
@@ -2,6 +2,9 @@ from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base
import os
# -----------------------------
# DATABASE URL
# -----------------------------
DATABASE_URL = os.getenv("DATABASE_URL")
engine = create_engine(
@@ -18,9 +21,24 @@ SessionLocal = sessionmaker(
Base = declarative_base()
# -----------------------------
# DB SESSION DEPENDENCY
# -----------------------------
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# -----------------------------
# INIT DB (CREA TABLAS)
# -----------------------------
def init_db():
# Importa modelos para que SQLAlchemy los registre
import app.db.models # noqa
# Crea todas las tablas si no existen
Base.metadata.create_all(bind=engine)