mirror of
https://github.com/khoaliber/LetterFeed.git
synced 2026-03-02 05:29:13 +00:00
feat: replace counting id with unique id
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
from nanoid import generate
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.logging import get_logger
|
||||
@@ -8,7 +9,7 @@ logger = get_logger(__name__)
|
||||
|
||||
|
||||
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."""
|
||||
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()
|
||||
|
||||
|
||||
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."""
|
||||
logger.info(
|
||||
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.commit()
|
||||
db.refresh(db_entry)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from nanoid import generate
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -9,7 +10,7 @@ from app.schemas.newsletters import NewsletterCreate, NewsletterUpdate
|
||||
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."""
|
||||
logger.debug(f"Querying for newsletter with id={newsletter_id}")
|
||||
result = (
|
||||
@@ -51,6 +52,7 @@ def create_newsletter(db: Session, newsletter: NewsletterCreate):
|
||||
"""Create a new newsletter."""
|
||||
logger.info(f"Creating new newsletter with name '{newsletter.name}'")
|
||||
db_newsletter = Newsletter(
|
||||
id=generate(size=10),
|
||||
name=newsletter.name,
|
||||
extract_content=newsletter.extract_content,
|
||||
move_to_folder=newsletter.move_to_folder,
|
||||
@@ -60,7 +62,7 @@ def create_newsletter(db: Session, newsletter: NewsletterCreate):
|
||||
db.refresh(db_newsletter)
|
||||
|
||||
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.commit()
|
||||
@@ -72,7 +74,7 @@ def create_newsletter(db: Session, newsletter: NewsletterCreate):
|
||||
|
||||
|
||||
def update_newsletter(
|
||||
db: Session, newsletter_id: int, newsletter_update: NewsletterUpdate
|
||||
db: Session, newsletter_id: str, newsletter_update: NewsletterUpdate
|
||||
):
|
||||
"""Update an existing newsletter."""
|
||||
logger.info(f"Updating newsletter with id={newsletter_id}")
|
||||
@@ -90,7 +92,7 @@ def update_newsletter(
|
||||
db.commit()
|
||||
|
||||
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.commit()
|
||||
@@ -99,7 +101,7 @@ def update_newsletter(
|
||||
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."""
|
||||
logger.info(f"Deleting newsletter with id={newsletter_id}")
|
||||
db_newsletter = get_newsletter(db, newsletter_id)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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 app.core.database import Base
|
||||
@@ -11,8 +11,8 @@ class Entry(Base):
|
||||
|
||||
__tablename__ = "entries"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
newsletter_id = Column(Integer, ForeignKey("newsletters.id"))
|
||||
id = Column(String, primary_key=True, index=True)
|
||||
newsletter_id = Column(String, ForeignKey("newsletters.id"))
|
||||
subject = Column(String)
|
||||
body = Column(Text)
|
||||
received_at = Column(
|
||||
|
||||
@@ -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 app.core.database import Base
|
||||
@@ -9,7 +9,7 @@ class Newsletter(Base):
|
||||
|
||||
__tablename__ = "newsletters"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
id = Column(String, primary_key=True, index=True)
|
||||
name = Column(String)
|
||||
move_to_folder = Column(String, nullable=True)
|
||||
is_active = Column(Boolean, default=True)
|
||||
@@ -28,8 +28,8 @@ class Sender(Base):
|
||||
|
||||
__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)
|
||||
newsletter_id = Column(Integer, ForeignKey("newsletters.id"), nullable=False)
|
||||
newsletter_id = Column(String, ForeignKey("newsletters.id"), nullable=False)
|
||||
|
||||
newsletter = relationship("Newsletter", back_populates="senders")
|
||||
|
||||
@@ -11,7 +11,7 @@ router = APIRouter()
|
||||
|
||||
|
||||
@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."""
|
||||
logger.info(f"Generating feed for newsletter_id={newsletter_id}")
|
||||
feed = generate_feed(db, newsletter_id)
|
||||
|
||||
@@ -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)
|
||||
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."""
|
||||
logger.info(f"Request to read newsletter with 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)
|
||||
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."""
|
||||
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)
|
||||
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."""
|
||||
logger.info(f"Request to delete newsletter with 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)
|
||||
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."""
|
||||
logger.info(f"Request to create entry for newsletter_id={newsletter_id}")
|
||||
|
||||
@@ -20,8 +20,8 @@ class EntryCreate(EntryBase):
|
||||
class Entry(EntryBase):
|
||||
"""Schema for retrieving an entry with its ID and newsletter ID."""
|
||||
|
||||
id: int
|
||||
newsletter_id: int
|
||||
id: str
|
||||
newsletter_id: str
|
||||
received_at: datetime.datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -18,8 +18,8 @@ class SenderCreate(SenderBase):
|
||||
class Sender(SenderBase):
|
||||
"""Schema for retrieving a sender with its ID and newsletter ID."""
|
||||
|
||||
id: int
|
||||
newsletter_id: int
|
||||
id: str
|
||||
newsletter_id: str
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -47,7 +47,7 @@ class NewsletterUpdate(NewsletterBase):
|
||||
class Newsletter(NewsletterBase):
|
||||
"""Schema for retrieving a newsletter with its ID, active status, senders, and entries count."""
|
||||
|
||||
id: int
|
||||
id: str
|
||||
is_active: bool
|
||||
senders: List[Sender] = []
|
||||
entries_count: int
|
||||
|
||||
@@ -7,7 +7,7 @@ from app.crud.entries import get_entries_by_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."""
|
||||
newsletter = get_newsletter(db, newsletter_id)
|
||||
if not newsletter:
|
||||
|
||||
@@ -8,6 +8,7 @@ dependencies = [
|
||||
"apscheduler>=3.11.0",
|
||||
"fastapi>=0.116.0",
|
||||
"feedgen>=1.0.0",
|
||||
"nanoid>=2.0.0",
|
||||
"pydantic-settings>=2.10.1",
|
||||
"python-dotenv>=1.1.1",
|
||||
"sqlalchemy>=2.0.41",
|
||||
|
||||
11
backend/uv.lock
generated
11
backend/uv.lock
generated
@@ -324,6 +324,7 @@ dependencies = [
|
||||
{ name = "apscheduler" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "feedgen" },
|
||||
{ name = "nanoid" },
|
||||
{ name = "pydantic-settings" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "sqlalchemy" },
|
||||
@@ -344,6 +345,7 @@ requires-dist = [
|
||||
{ name = "apscheduler", specifier = ">=3.11.0" },
|
||||
{ name = "fastapi", specifier = ">=0.116.0" },
|
||||
{ name = "feedgen", specifier = ">=1.0.0" },
|
||||
{ name = "nanoid", specifier = ">=2.0.0" },
|
||||
{ name = "pydantic-settings", specifier = ">=2.10.1" },
|
||||
{ name = "python-dotenv", specifier = ">=1.1.1" },
|
||||
{ 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" },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "nodeenv"
|
||||
version = "1.9.1"
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
|
||||
export interface Sender {
|
||||
id: number;
|
||||
id: string;
|
||||
email: string;
|
||||
newsletter_id: number;
|
||||
newsletter_id: string;
|
||||
}
|
||||
|
||||
export interface Newsletter {
|
||||
id: number
|
||||
id: string
|
||||
name: string
|
||||
is_active: boolean
|
||||
move_to_folder?: string | null
|
||||
extract_content: boolean
|
||||
senders: { id: number; email: string }[]
|
||||
senders: { id: string; email: string }[]
|
||||
entries_count: number
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ export async function createNewsletter(newsletter: NewsletterCreate): Promise<Ne
|
||||
}, "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}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
@@ -116,7 +116,7 @@ export async function updateNewsletter(id: number, newsletter: NewsletterUpdate)
|
||||
}, "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}`, {
|
||||
method: 'DELETE',
|
||||
}, "Failed to delete newsletter");
|
||||
@@ -152,6 +152,6 @@ export async function processEmails(): Promise<{ message: string }> {
|
||||
}, "Failed to process emails");
|
||||
}
|
||||
|
||||
export function getFeedUrl(newsletterId: number): string {
|
||||
export function getFeedUrl(newsletterId: string): string {
|
||||
return `${API_BASE_URL}/feeds/${newsletterId}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user