diff --git a/conexionbd.py b/conexionbd.py new file mode 100644 index 0000000..c28041e --- /dev/null +++ b/conexionbd.py @@ -0,0 +1,10 @@ +from sqlalchemy import create_engine +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, declarative_base + +SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db" + +engine = create_engine(SQLALCHEMY_DATABASE_URL) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +Base = declarative_base() diff --git a/main.py b/main.py new file mode 100644 index 0000000..57ee586 --- /dev/null +++ b/main.py @@ -0,0 +1,40 @@ +from fastapi import FastAPI, File, UploadFile +from fastapi.staticfiles import StaticFiles +from starlette.responses import FileResponse +from src.routers.noticiarouters import router as noticia_router +from conexionbd import Base, engine +import os + +# Crear todas las tablas en la base de datos +Base.metadata.create_all(bind=engine) + +# Crear la instancia de la aplicación FastAPI +app = FastAPI() + +# Montar directorio estático +app.mount("/static", StaticFiles(directory="static"), name="static") + +# Incluir el router de noticias +app.include_router(noticia_router, prefix="/api/v1") + +# Endpoint para servir el archivo noticiaver.html +@app.get("/") +async def get_index(): + return FileResponse("src/views/noticiaver.html") + +# Endpoint para servir el archivo crearnoticia.html +@app.get("/crearnoticia.html") +async def get_create_news(): + return FileResponse("src/views/crearnoticia.html") + +# Endpoint para manejar la subida de archivos +@app.post("/api/v1/noticias/") +async def create_noticia(titulo: str, cuerpo: str, archivo: UploadFile = File(...)): + # Guarda el archivo + upload_folder = "/static/images" + os.makedirs(upload_folder, exist_ok=True) + file_location = f"{upload_folder}/{archivo.filename}" + with open(file_location, "wb") as file: + file.write(archivo.file.read()) + + return {"titulo": titulo, "cuerpo": cuerpo, "archivo": f"/static/images/{archivo.filename}"} diff --git a/src/controllers/noticiacontroller.py b/src/controllers/noticiacontroller.py new file mode 100644 index 0000000..98f42c9 --- /dev/null +++ b/src/controllers/noticiacontroller.py @@ -0,0 +1,14 @@ +import sys +import os + +# Agregar la ruta del directorio raíz de tu proyecto al sys.path +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + +from fastapi import APIRouter +from src.models.noticiamodel import get_crearnoticia + +router = APIRouter() + +@router.get("/") +async def get_index(): + return get_crearnoticia() diff --git a/src/models/noticia.py b/src/models/noticia.py new file mode 100644 index 0000000..5f84133 --- /dev/null +++ b/src/models/noticia.py @@ -0,0 +1,17 @@ +import sys +import os + +# Agregar la ruta del directorio raíz de tu proyecto al sys.path +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + +from sqlalchemy import Column, Integer, String, Date +from conexionbd import Base + +class Noticia(Base): + __tablename__ = 'noticias' + + id_noticia = Column(Integer, primary_key=True, index=True) + titulo = Column(String, index=True) + cuerpo = Column(String) + archivo = Column(String) # Ruta del archivo + fecha = Column(Date) diff --git a/src/models/noticiamodel.py b/src/models/noticiamodel.py new file mode 100644 index 0000000..3adc0b1 --- /dev/null +++ b/src/models/noticiamodel.py @@ -0,0 +1,40 @@ +import sys +import os + +# Agregar la ruta del directorio raíz de tu proyecto al sys.path +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + +from sqlalchemy.orm import Session +from fastapi import UploadFile +from src.models.noticia import Noticia +from src.models.schemas import NoticiaCreate +from fastapi.responses import FileResponse +import shutil + +def save_file(file: UploadFile, destination: str): + with open(destination, "wb") as buffer: + shutil.copyfileobj(file.file, buffer) + return destination + +def get_crearnoticia(): + return FileResponse("src/views/crearnoticia.html") + +def create_noticia(db: Session, noticia: NoticiaCreate, file: UploadFile): + file_path = save_file(file, f"static/images/{file.filename}") + db_noticia = Noticia(id_noticia=noticia.id_noticia, titulo=noticia.titulo, cuerpo=noticia.cuerpo, + archivo=file_path, fecha=noticia.fecha) + db.add(db_noticia) + db.commit() + db.refresh(db_noticia) + return db_noticia + +def get_noticias(db: Session): + return db.query(Noticia).all() + +def get_noticia(db: Session, noticia_id: int): + return db.query(Noticia).filter(Noticia.id_noticia == noticia_id).first() + +def delete_noticia(db: Session, noticia_id: int): + db.query(Noticia).filter(Noticia.id_noticia == noticia_id).delete() + db.commit() + diff --git a/src/models/schemas.py b/src/models/schemas.py new file mode 100644 index 0000000..0d5c23a --- /dev/null +++ b/src/models/schemas.py @@ -0,0 +1,21 @@ +from pydantic import BaseModel +from datetime import date + +# Actúa como una clase base, reduciendo la duplicación de código y asegurando que los campos comunes estén centralizados. +class NoticiaBase(BaseModel): + titulo: str + cuerpo: str + archivo: str + fecha: date + +# Extiende NoticiaBase y añade campos específicos necesarios solo para la creación. +class NoticiaCreate(NoticiaBase): + id_noticia: int + +# Extiende NoticiaBase y añade campos necesarios para la respuesta de la API, +# junto con configuraciones específicas para la interoperabilidad con SQLAlchemy. +class NoticiaSchema(NoticiaBase): + id_noticia: int + + class Config: + from_attributes = True diff --git a/src/routers/noticiarouters.py b/src/routers/noticiarouters.py new file mode 100644 index 0000000..3c6dd4e --- /dev/null +++ b/src/routers/noticiarouters.py @@ -0,0 +1,81 @@ +import sys +import os +import shutil +from fastapi import HTTPException, Depends, APIRouter, UploadFile, Form +from sqlalchemy.orm import Session +from fastapi.responses import JSONResponse +from fastapi import File + +# Agregar la ruta del directorio raíz de tu proyecto al sys.path +sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + +from conexionbd import SessionLocal +from src.models.noticiamodel import create_noticia as create_noticia_db, get_noticias as get_noticias_db, get_noticia as get_noticia_db, delete_noticia as delete_noticia_db +from src.models.schemas import NoticiaCreate, NoticiaSchema +from src.models.noticia import Noticia + +router = APIRouter() + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + +def get_next_noticia_id(db: Session) -> int: + """Obtiene el próximo ID disponible para una nueva noticia.""" + last_noticia = db.query(Noticia).order_by(Noticia.id_noticia.desc()).first() + if last_noticia: + return last_noticia.id_noticia + 1 + else: + return 1 + +@router.post("/noticias/", response_model=NoticiaSchema) +def create_noticia( + titulo: str = Form(...), + cuerpo: str = Form(...), + fecha: str = Form(...), + archivo: UploadFile = File(...), + db: Session = Depends(get_db) +): + # Obtener el próximo ID disponible + id_noticia = get_next_noticia_id(db) + + # Crear el directorio de imágenes si no existe + upload_folder = "static/images" + os.makedirs(upload_folder, exist_ok=True) + + # Guardar el archivo en el directorio y obtener su ruta + file_path = os.path.join(upload_folder, archivo.filename) + with open(file_path, "wb") as file_object: + shutil.copyfileobj(archivo.file, file_object) + + # Crear el objeto NoticiaCreate + noticia_data = NoticiaCreate(id_noticia=id_noticia, titulo=titulo, cuerpo=cuerpo, archivo=file_path, fecha=fecha) + return create_noticia_db(db, noticia_data, archivo) + +@router.get("/noticias/", response_model=list[NoticiaSchema]) +def read_noticias(db: Session = Depends(get_db)): + return get_noticias_db(db) + +@router.get("/noticias/{noticia_id}", response_model=NoticiaSchema) +def read_publicacion(noticia_id: int, db: Session = Depends(get_db)): + db_noticia = get_noticia_db(db, noticia_id) + if db_noticia is None: + raise HTTPException(status_code=404, detail="Publicación no encontrada") + return db_noticia + +@router.delete("/noticias/{noticia_id}", response_model=dict) +def delete_noticia(noticia_id: int, db: Session = Depends(get_db)): + db_noticia = get_noticia_db(db, noticia_id) + if db_noticia is None: + raise HTTPException(status_code=404, detail="Publicación no encontrada") + delete_noticia_db(db, noticia_id) + return {"detail": "Noticia eliminada"} + +@router.get("/noticias/buscar/", response_model=list[NoticiaSchema]) +def search_noticias(query: str, db: Session = Depends(get_db)): + noticias = db.query(Noticia).filter(Noticia.titulo.ilike(f"%{query}%")).all() + return noticias + diff --git a/src/views/crearnoticia.html b/src/views/crearnoticia.html new file mode 100644 index 0000000..f3d0d06 --- /dev/null +++ b/src/views/crearnoticia.html @@ -0,0 +1,65 @@ + + + + + + Crear Noticia + + + + + +
+

Crear Noticia

+
+
+ +

+
+
+ +

+
+
+ +

+
+
+ +

+
+ + +
+
+ + + + + + diff --git a/src/views/noticiaver.html b/src/views/noticiaver.html new file mode 100644 index 0000000..135dfb7 --- /dev/null +++ b/src/views/noticiaver.html @@ -0,0 +1,30 @@ + + + + + + Noticias + + + + +
+
+

Hola [Name], aquí están las noticias más relevantes

+
+ + +
+ + +
+ +
+
+ + + diff --git a/static/images/bell-icon.png b/static/images/bell-icon.png new file mode 100644 index 0000000..4e88a53 Binary files /dev/null and b/static/images/bell-icon.png differ diff --git a/static/images/info.txt b/static/images/info.txt new file mode 100644 index 0000000..e88fee7 --- /dev/null +++ b/static/images/info.txt @@ -0,0 +1 @@ +**aqui van las imagenes que se cargan en las noticias** diff --git a/static/images/trash-icon.png b/static/images/trash-icon.png new file mode 100644 index 0000000..7fde180 Binary files /dev/null and b/static/images/trash-icon.png differ diff --git a/static/scriptver.js b/static/scriptver.js new file mode 100644 index 0000000..5740d71 --- /dev/null +++ b/static/scriptver.js @@ -0,0 +1,100 @@ +document.addEventListener('DOMContentLoaded', () => { + obtenerNoticias(); + + const searchInput = document.getElementById('search'); + searchInput.addEventListener('keypress', (event) => { + if (event.key === 'Enter') { + buscarNoticias(searchInput.value); + } + }); + + const searchButton = document.getElementById('search-button'); + searchButton.addEventListener('click', () => { + buscarNoticias(searchInput.value); + }); + + const createButton = document.getElementById('create-button'); + createButton.addEventListener('click', () => { + window.location.href = "/crearnoticia.html"; + }); +}); + +async function obtenerNoticias() { + try { + const respuesta = await fetch('/api/v1/noticias/'); + const datos = await respuesta.json(); + + if (respuesta.ok) { + mostrarNoticias(datos); + } else { + console.error('Error al obtener las noticias:', datos); + } + } catch (error) { + console.error('Error al obtener las noticias:', error); + } +} + +async function buscarNoticias(query) { + try { + const respuesta = await fetch(`/api/v1/noticias/buscar/?query=${encodeURIComponent(query)}`); + const datos = await respuesta.json(); + + if (respuesta.ok) { + mostrarNoticias(datos); + } else { + console.error('Error al buscar noticias:', datos); + } + } catch (error) { + console.error('Error al buscar noticias:', error); + } +} + +function mostrarNoticias(noticias) { + const noticiasList = document.getElementById('noticias-list'); + noticiasList.innerHTML = ''; + + noticias.forEach(noticia => { + const noticiaHTML = ` +
+ ${noticia.titulo} +
+

${noticia.titulo}

+

${noticia.cuerpo}

+
+
+ + +
+
+ `; + noticiasList.insertAdjacentHTML('beforeend', noticiaHTML); + }); +} + +async function eliminarNoticia(id_noticia) { + if (!confirm('¿Estás seguro de que deseas eliminar esta noticia?')) { + return; + } + + try { + const respuesta = await fetch(`/api/v1/noticias/${id_noticia}`, { + method: 'DELETE' + }); + + if (respuesta.ok) { + alert('Noticia eliminada'); + obtenerNoticias(); + } else { + const errorData = await respuesta.json(); + console.error('Error al eliminar la noticia:', errorData); + alert('Error al eliminar la noticia'); + } + } catch (error) { + console.error('Error al eliminar la noticia:', error); + alert('Error al eliminar la noticia'); + } +} diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..b12f718 --- /dev/null +++ b/static/style.css @@ -0,0 +1,76 @@ +/* styles.css */ +body { + font-family: Arial, sans-serif; + background-color: #4B0082; + color: white; + margin: 0; + padding: 0; + box-sizing: border-box; +} + +.container { + padding: 20px; +} + +header { + text-align: center; + margin-bottom: 20px; +} + +input[type="text"] { + padding: 10px; + width: 300px; + border-radius: 5px; + border: 1px solid #ccc; +} + +button { + padding: 10px; + border: none; + border-radius: 5px; + background-color: #8A2BE2; + color: white; + cursor: pointer; +} + +button:hover { + background-color: #6A1BBE; +} + +.noticia { + background-color: #800080; + border-radius: 10px; + margin-bottom: 20px; + padding: 15px; + display: flex; + align-items: center; +} + +.noticia img { + width: 150px; + height: 150px; + object-fit: cover; /* Esto asegura que la imagen se recorte */ + border-radius: 10px; + margin-right: 20px; +} + +.noticia-info { + flex-grow: 1; +} + +.actions { + display: flex; + gap: 10px; +} + +.actions button { + background-color: transparent; + padding: 0; + border: none; + cursor: pointer; +} + +.actions button img { + width: 30px; + height: 30px; +}