feat: replace counting id with unique id

This commit is contained in:
Leon
2025-07-17 15:13:29 +02:00
parent 9d982314d8
commit d8bbfe88fa
12 changed files with 48 additions and 33 deletions

View File

@@ -1,3 +1,4 @@
from nanoid import generate
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.logging import get_logger from app.core.logging import get_logger
@@ -8,7 +9,7 @@ logger = get_logger(__name__)
def get_entries_by_newsletter( def get_entries_by_newsletter(
db: Session, newsletter_id: int, skip: int = 0, limit: int = 100 db: Session, newsletter_id: str, skip: int = 0, limit: int = 100
): ):
"""Retrieve entries for a specific newsletter.""" """Retrieve entries for a specific newsletter."""
logger.debug( logger.debug(
@@ -29,12 +30,12 @@ def get_entry_by_message_id(db: Session, message_id: str):
return db.query(Entry).filter(Entry.message_id == message_id).first() return db.query(Entry).filter(Entry.message_id == message_id).first()
def create_entry(db: Session, entry: EntryCreate, newsletter_id: int): def create_entry(db: Session, entry: EntryCreate, newsletter_id: str):
"""Create a new entry for a newsletter.""" """Create a new entry for a newsletter."""
logger.info( logger.info(
f"Creating new entry for newsletter_id={newsletter_id} with subject '{entry.subject}'" f"Creating new entry for newsletter_id={newsletter_id} with subject '{entry.subject}'"
) )
db_entry = Entry(**entry.model_dump(), newsletter_id=newsletter_id) db_entry = Entry(id=generate(), **entry.model_dump(), newsletter_id=newsletter_id)
db.add(db_entry) db.add(db_entry)
db.commit() db.commit()
db.refresh(db_entry) db.refresh(db_entry)

View File

@@ -1,3 +1,4 @@
from nanoid import generate
from sqlalchemy import func from sqlalchemy import func
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -9,7 +10,7 @@ from app.schemas.newsletters import NewsletterCreate, NewsletterUpdate
logger = get_logger(__name__) logger = get_logger(__name__)
def get_newsletter(db: Session, newsletter_id: int): def get_newsletter(db: Session, newsletter_id: str):
"""Retrieve a single newsletter by its ID.""" """Retrieve a single newsletter by its ID."""
logger.debug(f"Querying for newsletter with id={newsletter_id}") logger.debug(f"Querying for newsletter with id={newsletter_id}")
result = ( result = (
@@ -51,6 +52,7 @@ def create_newsletter(db: Session, newsletter: NewsletterCreate):
"""Create a new newsletter.""" """Create a new newsletter."""
logger.info(f"Creating new newsletter with name '{newsletter.name}'") logger.info(f"Creating new newsletter with name '{newsletter.name}'")
db_newsletter = Newsletter( db_newsletter = Newsletter(
id=generate(size=10),
name=newsletter.name, name=newsletter.name,
extract_content=newsletter.extract_content, extract_content=newsletter.extract_content,
move_to_folder=newsletter.move_to_folder, move_to_folder=newsletter.move_to_folder,
@@ -60,7 +62,7 @@ def create_newsletter(db: Session, newsletter: NewsletterCreate):
db.refresh(db_newsletter) db.refresh(db_newsletter)
for email in newsletter.sender_emails: for email in newsletter.sender_emails:
db_sender = Sender(email=email, newsletter_id=db_newsletter.id) db_sender = Sender(id=generate(), email=email, newsletter_id=db_newsletter.id)
db.add(db_sender) db.add(db_sender)
db.commit() db.commit()
@@ -72,7 +74,7 @@ def create_newsletter(db: Session, newsletter: NewsletterCreate):
def update_newsletter( def update_newsletter(
db: Session, newsletter_id: int, newsletter_update: NewsletterUpdate db: Session, newsletter_id: str, newsletter_update: NewsletterUpdate
): ):
"""Update an existing newsletter.""" """Update an existing newsletter."""
logger.info(f"Updating newsletter with id={newsletter_id}") logger.info(f"Updating newsletter with id={newsletter_id}")
@@ -90,7 +92,7 @@ def update_newsletter(
db.commit() db.commit()
for email in newsletter_update.sender_emails: for email in newsletter_update.sender_emails:
db_sender = Sender(email=email, newsletter_id=db_newsletter.id) db_sender = Sender(id=generate(), email=email, newsletter_id=db_newsletter.id)
db.add(db_sender) db.add(db_sender)
db.commit() db.commit()
@@ -99,7 +101,7 @@ def update_newsletter(
return get_newsletter(db, newsletter_id) return get_newsletter(db, newsletter_id)
def delete_newsletter(db: Session, newsletter_id: int): def delete_newsletter(db: Session, newsletter_id: str):
"""Delete a newsletter by its ID.""" """Delete a newsletter by its ID."""
logger.info(f"Deleting newsletter with id={newsletter_id}") logger.info(f"Deleting newsletter with id={newsletter_id}")
db_newsletter = get_newsletter(db, newsletter_id) db_newsletter = get_newsletter(db, newsletter_id)

View File

@@ -1,6 +1,6 @@
import datetime import datetime
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text from sqlalchemy import Column, DateTime, ForeignKey, String, Text
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from app.core.database import Base from app.core.database import Base
@@ -11,8 +11,8 @@ class Entry(Base):
__tablename__ = "entries" __tablename__ = "entries"
id = Column(Integer, primary_key=True, index=True) id = Column(String, primary_key=True, index=True)
newsletter_id = Column(Integer, ForeignKey("newsletters.id")) newsletter_id = Column(String, ForeignKey("newsletters.id"))
subject = Column(String) subject = Column(String)
body = Column(Text) body = Column(Text)
received_at = Column( received_at = Column(

View File

@@ -1,4 +1,4 @@
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String from sqlalchemy import Boolean, Column, ForeignKey, String
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from app.core.database import Base from app.core.database import Base
@@ -9,7 +9,7 @@ class Newsletter(Base):
__tablename__ = "newsletters" __tablename__ = "newsletters"
id = Column(Integer, primary_key=True, index=True) id = Column(String, primary_key=True, index=True)
name = Column(String) name = Column(String)
move_to_folder = Column(String, nullable=True) move_to_folder = Column(String, nullable=True)
is_active = Column(Boolean, default=True) is_active = Column(Boolean, default=True)
@@ -28,8 +28,8 @@ class Sender(Base):
__tablename__ = "senders" __tablename__ = "senders"
id = Column(Integer, primary_key=True, index=True) id = Column(String, primary_key=True, index=True)
email = Column(String, unique=True, index=True, nullable=False) email = Column(String, unique=True, index=True, nullable=False)
newsletter_id = Column(Integer, ForeignKey("newsletters.id"), nullable=False) newsletter_id = Column(String, ForeignKey("newsletters.id"), nullable=False)
newsletter = relationship("Newsletter", back_populates="senders") newsletter = relationship("Newsletter", back_populates="senders")

View File

@@ -11,7 +11,7 @@ router = APIRouter()
@router.get("/feeds/{newsletter_id}") @router.get("/feeds/{newsletter_id}")
def get_newsletter_feed(newsletter_id: int, db: Session = Depends(get_db)): def get_newsletter_feed(newsletter_id: str, db: Session = Depends(get_db)):
"""Generate an Atom feed for a specific newsletter.""" """Generate an Atom feed for a specific newsletter."""
logger.info(f"Generating feed for newsletter_id={newsletter_id}") logger.info(f"Generating feed for newsletter_id={newsletter_id}")
feed = generate_feed(db, newsletter_id) feed = generate_feed(db, newsletter_id)

View File

@@ -38,7 +38,7 @@ def read_newsletters(skip: int = 0, limit: int = 100, db: Session = Depends(get_
@router.get("/newsletters/{newsletter_id}", response_model=Newsletter) @router.get("/newsletters/{newsletter_id}", response_model=Newsletter)
def read_newsletter(newsletter_id: int, db: Session = Depends(get_db)): def read_newsletter(newsletter_id: str, db: Session = Depends(get_db)):
"""Retrieve a single newsletter by its ID.""" """Retrieve a single newsletter by its ID."""
logger.info(f"Request to read newsletter with id={newsletter_id}") logger.info(f"Request to read newsletter with id={newsletter_id}")
db_newsletter = get_newsletter(db, newsletter_id=newsletter_id) db_newsletter = get_newsletter(db, newsletter_id=newsletter_id)
@@ -50,7 +50,7 @@ def read_newsletter(newsletter_id: int, db: Session = Depends(get_db)):
@router.put("/newsletters/{newsletter_id}", response_model=Newsletter) @router.put("/newsletters/{newsletter_id}", response_model=Newsletter)
def update_existing_newsletter( def update_existing_newsletter(
newsletter_id: int, newsletter: NewsletterUpdate, db: Session = Depends(get_db) newsletter_id: str, newsletter: NewsletterUpdate, db: Session = Depends(get_db)
): ):
"""Update an existing newsletter.""" """Update an existing newsletter."""
logger.info(f"Request to update newsletter with id={newsletter_id}") logger.info(f"Request to update newsletter with id={newsletter_id}")
@@ -64,7 +64,7 @@ def update_existing_newsletter(
@router.delete("/newsletters/{newsletter_id}", response_model=Newsletter) @router.delete("/newsletters/{newsletter_id}", response_model=Newsletter)
def delete_existing_newsletter(newsletter_id: int, db: Session = Depends(get_db)): def delete_existing_newsletter(newsletter_id: str, db: Session = Depends(get_db)):
"""Delete a newsletter by its ID.""" """Delete a newsletter by its ID."""
logger.info(f"Request to delete newsletter with id={newsletter_id}") logger.info(f"Request to delete newsletter with id={newsletter_id}")
db_newsletter = delete_newsletter(db, newsletter_id=newsletter_id) db_newsletter = delete_newsletter(db, newsletter_id=newsletter_id)
@@ -76,7 +76,7 @@ def delete_existing_newsletter(newsletter_id: int, db: Session = Depends(get_db)
@router.post("/newsletters/{newsletter_id}/entries", response_model=Entry) @router.post("/newsletters/{newsletter_id}/entries", response_model=Entry)
def create_newsletter_entry( def create_newsletter_entry(
newsletter_id: int, entry: EntryCreate, db: Session = Depends(get_db) newsletter_id: str, entry: EntryCreate, db: Session = Depends(get_db)
): ):
"""Create a new entry for a specific newsletter.""" """Create a new entry for a specific newsletter."""
logger.info(f"Request to create entry for newsletter_id={newsletter_id}") logger.info(f"Request to create entry for newsletter_id={newsletter_id}")

View File

@@ -20,8 +20,8 @@ class EntryCreate(EntryBase):
class Entry(EntryBase): class Entry(EntryBase):
"""Schema for retrieving an entry with its ID and newsletter ID.""" """Schema for retrieving an entry with its ID and newsletter ID."""
id: int id: str
newsletter_id: int newsletter_id: str
received_at: datetime.datetime received_at: datetime.datetime
model_config = ConfigDict(from_attributes=True) model_config = ConfigDict(from_attributes=True)

View File

@@ -18,8 +18,8 @@ class SenderCreate(SenderBase):
class Sender(SenderBase): class Sender(SenderBase):
"""Schema for retrieving a sender with its ID and newsletter ID.""" """Schema for retrieving a sender with its ID and newsletter ID."""
id: int id: str
newsletter_id: int newsletter_id: str
model_config = ConfigDict(from_attributes=True) model_config = ConfigDict(from_attributes=True)
@@ -47,7 +47,7 @@ class NewsletterUpdate(NewsletterBase):
class Newsletter(NewsletterBase): class Newsletter(NewsletterBase):
"""Schema for retrieving a newsletter with its ID, active status, senders, and entries count.""" """Schema for retrieving a newsletter with its ID, active status, senders, and entries count."""
id: int id: str
is_active: bool is_active: bool
senders: List[Sender] = [] senders: List[Sender] = []
entries_count: int entries_count: int

View File

@@ -7,7 +7,7 @@ from app.crud.entries import get_entries_by_newsletter
from app.crud.newsletters import get_newsletter from app.crud.newsletters import get_newsletter
def generate_feed(db: Session, newsletter_id: int): def generate_feed(db: Session, newsletter_id: str):
"""Generate an Atom feed for a given newsletter.""" """Generate an Atom feed for a given newsletter."""
newsletter = get_newsletter(db, newsletter_id) newsletter = get_newsletter(db, newsletter_id)
if not newsletter: if not newsletter:

View File

@@ -8,6 +8,7 @@ dependencies = [
"apscheduler>=3.11.0", "apscheduler>=3.11.0",
"fastapi>=0.116.0", "fastapi>=0.116.0",
"feedgen>=1.0.0", "feedgen>=1.0.0",
"nanoid>=2.0.0",
"pydantic-settings>=2.10.1", "pydantic-settings>=2.10.1",
"python-dotenv>=1.1.1", "python-dotenv>=1.1.1",
"sqlalchemy>=2.0.41", "sqlalchemy>=2.0.41",

11
backend/uv.lock generated
View File

@@ -324,6 +324,7 @@ dependencies = [
{ name = "apscheduler" }, { name = "apscheduler" },
{ name = "fastapi" }, { name = "fastapi" },
{ name = "feedgen" }, { name = "feedgen" },
{ name = "nanoid" },
{ name = "pydantic-settings" }, { name = "pydantic-settings" },
{ name = "python-dotenv" }, { name = "python-dotenv" },
{ name = "sqlalchemy" }, { name = "sqlalchemy" },
@@ -344,6 +345,7 @@ requires-dist = [
{ name = "apscheduler", specifier = ">=3.11.0" }, { name = "apscheduler", specifier = ">=3.11.0" },
{ name = "fastapi", specifier = ">=0.116.0" }, { name = "fastapi", specifier = ">=0.116.0" },
{ name = "feedgen", specifier = ">=1.0.0" }, { name = "feedgen", specifier = ">=1.0.0" },
{ name = "nanoid", specifier = ">=2.0.0" },
{ name = "pydantic-settings", specifier = ">=2.10.1" }, { name = "pydantic-settings", specifier = ">=2.10.1" },
{ name = "python-dotenv", specifier = ">=1.1.1" }, { name = "python-dotenv", specifier = ">=1.1.1" },
{ name = "sqlalchemy", specifier = ">=2.0.41" }, { name = "sqlalchemy", specifier = ">=2.0.41" },
@@ -418,6 +420,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/4e/0b/942cb7278d6caad79343ad2ddd636ed204a47909b969d19114a3097f5aa3/lxml_html_clean-0.4.2-py3-none-any.whl", hash = "sha256:74ccfba277adcfea87a1e9294f47dd86b05d65b4da7c5b07966e3d5f3be8a505", size = 14184, upload-time = "2025-04-09T11:33:57.988Z" }, { url = "https://files.pythonhosted.org/packages/4e/0b/942cb7278d6caad79343ad2ddd636ed204a47909b969d19114a3097f5aa3/lxml_html_clean-0.4.2-py3-none-any.whl", hash = "sha256:74ccfba277adcfea87a1e9294f47dd86b05d65b4da7c5b07966e3d5f3be8a505", size = 14184, upload-time = "2025-04-09T11:33:57.988Z" },
] ]
[[package]]
name = "nanoid"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/0250bf5935d88e214df469d35eccc0f6ff7e9db046fc8a9aeb4b2a192775/nanoid-2.0.0.tar.gz", hash = "sha256:5a80cad5e9c6e9ae3a41fa2fb34ae189f7cb420b2a5d8f82bd9d23466e4efa68", size = 3290, upload-time = "2018-11-20T14:45:51.578Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2e/0d/8630f13998638dc01e187fadd2e5c6d42d127d08aeb4943d231664d6e539/nanoid-2.0.0-py3-none-any.whl", hash = "sha256:90aefa650e328cffb0893bbd4c236cfd44c48bc1f2d0b525ecc53c3187b653bb", size = 5844, upload-time = "2018-11-20T14:45:50.165Z" },
]
[[package]] [[package]]
name = "nodeenv" name = "nodeenv"
version = "1.9.1" version = "1.9.1"

View File

@@ -1,18 +1,18 @@
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL; const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL;
export interface Sender { export interface Sender {
id: number; id: string;
email: string; email: string;
newsletter_id: number; newsletter_id: string;
} }
export interface Newsletter { export interface Newsletter {
id: number id: string
name: string name: string
is_active: boolean is_active: boolean
move_to_folder?: string | null move_to_folder?: string | null
extract_content: boolean extract_content: boolean
senders: { id: number; email: string }[] senders: { id: string; email: string }[]
entries_count: number entries_count: number
} }
@@ -106,7 +106,7 @@ export async function createNewsletter(newsletter: NewsletterCreate): Promise<Ne
}, "Failed to create newsletter"); }, "Failed to create newsletter");
} }
export async function updateNewsletter(id: number, newsletter: NewsletterUpdate): Promise<Newsletter> { export async function updateNewsletter(id: string, newsletter: NewsletterUpdate): Promise<Newsletter> {
return fetcher<Newsletter>(`${API_BASE_URL}/newsletters/${id}`, { return fetcher<Newsletter>(`${API_BASE_URL}/newsletters/${id}`, {
method: 'PUT', method: 'PUT',
headers: { headers: {
@@ -116,7 +116,7 @@ export async function updateNewsletter(id: number, newsletter: NewsletterUpdate)
}, "Failed to update newsletter"); }, "Failed to update newsletter");
} }
export async function deleteNewsletter(id: number): Promise<void> { export async function deleteNewsletter(id: string): Promise<void> {
await fetcher<void>(`${API_BASE_URL}/newsletters/${id}`, { await fetcher<void>(`${API_BASE_URL}/newsletters/${id}`, {
method: 'DELETE', method: 'DELETE',
}, "Failed to delete newsletter"); }, "Failed to delete newsletter");
@@ -152,6 +152,6 @@ export async function processEmails(): Promise<{ message: string }> {
}, "Failed to process emails"); }, "Failed to process emails");
} }
export function getFeedUrl(newsletterId: number): string { export function getFeedUrl(newsletterId: string): string {
return `${API_BASE_URL}/feeds/${newsletterId}`; return `${API_BASE_URL}/feeds/${newsletterId}`;
} }