Skip to content

Commit

Permalink
finsihed switch
Browse files Browse the repository at this point in the history
  • Loading branch information
dorukgezici committed Oct 14, 2024
1 parent 1973fc5 commit 18f4f88
Show file tree
Hide file tree
Showing 13 changed files with 163 additions and 179 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/backend-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: "Backend: Build & Push"

on:
workflow_dispatch:
push:
branches:
- main

env:
PROJECT_NAME: subabot

jobs:
build_and_push:
name: Build & Push Docker image
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build, tag and push Docker image
uses: docker/build-push-action@v5
with:
context: ./backend
push: true
tags: ${{ github.repository_owner }}/${{ env.PROJECT_NAME }}:${{ github.sha }}
platforms: linux/arm64,linux/amd64
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: "Backend: Lint & Test"

on:
workflow_dispatch:
push:
branches:
- main
Expand All @@ -22,7 +23,7 @@ jobs:
python-version-file: "backend/.python-version"

- name: Install the project
run: uv sync --all-extras --dev
run: cd backend && uv sync --all-extras --dev

- name: Lint the project
run: uv run task lint
Expand Down
57 changes: 0 additions & 57 deletions .github/workflows/backend.yml

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: "Prefect: Deploy Flow"

on:
workflow_dispatch:
push:
branches:
- main
Expand Down
39 changes: 28 additions & 11 deletions backend/subabot/db.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from abc import ABC
from dataclasses import dataclass
from typing import Annotated
from typing import Annotated, Self, Sequence

from fastapi import Depends
from sqlmodel import Session, create_engine, select
Expand All @@ -19,31 +18,49 @@ def get_session():


@dataclass
class DBMixin(ABC):
class DBMixin:
"""Mixin for SQLModel classes"""

key: str

@classmethod
def read(cls, key: str):
with Session(engine) as session:
def get(cls, key: str, session: Session | None = None) -> Self:
with session or Session(engine) as session:
if obj := session.exec(select(cls).where(cls.key == key)).first():
return obj

raise ValueError(f"{cls.__name__} with key '{key}' not found.")

@classmethod
def upsert(cls, key: str, **fields):
with Session(engine) as session:
if (obj := session.exec(select(cls).where(cls.key == key)).first()) is None:
obj = cls(key=key, **fields)
def delete(cls, key: str, session: Session | None = None) -> None:
with session or Session(engine) as session:
if obj := session.exec(select(cls).where(cls.key == key)).first():
session.delete(obj)
session.commit()
return

raise ValueError(f"{cls.__name__} with key '{key}' not found.")

@classmethod
def list(cls, session: Session | None = None) -> Sequence[Self]:
with session or Session(engine) as session:
return session.exec(select(cls)).all()

@classmethod
def upsert(cls, session: Session | None = None, **fields) -> Self:
with session or Session(engine) as session:
if obj := session.exec(select(cls).where(cls.key == fields.get("key"))).first():
for field_key, value in fields.items():
setattr(obj, field_key, value)
else:
for key, value in fields.items():
setattr(obj, key, value)
obj = cls(**fields)

if "updated_at" in getattr(cls, "model_fields"):
setattr(obj, "updated_at", now_timestamp())

session.add(obj)
session.commit()
return obj


__all__ = ["engine", "Session", "SessionDep", "DBMixin"]
14 changes: 7 additions & 7 deletions backend/subabot/rss/crawler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,25 @@
from subabot.utils import now_timestamp


async def crawl_feed(feed: Feed, keywords: Sequence[Keyword]) -> list[dict]:
async def crawl_feed(feed: Feed, keywords: Sequence[Keyword]) -> Sequence[dict]:
"""Runs the crawler for the given feed and keywords."""

url = feed.key
data = FeedParserDict(await asyncify(parse)(url))
data = FeedParserDict(await asyncify(parse)(url)) # type: ignore
feed_info, entries = data.feed, list(data.entries)
assert isinstance(feed_info, dict)
assert isinstance(entries, list)

Crawl.upsert(url, feed=feed_info, entries=entries)
Feed.upsert(url, title=feed_info.get("title", feed.title), refreshed_at=now_timestamp())
Crawl.upsert(key=url, feed=feed_info, entries=entries)
Feed.upsert(key=url, title=feed_info.get("title", feed.title), refreshed_at=now_timestamp())

matches: list[tuple] = []
for keyword in keywords:
Keyword.upsert(keyword.key, value=keyword.value, checked_at=now_timestamp())
Keyword.upsert(key=keyword.key, value=keyword.value, checked_at=now_timestamp())
keyword_matches = [path for path in find_matches(entries, keyword.value)]
matches.extend(keyword_matches)
Search.upsert(
keyword.key,
key=keyword.key,
keyword=keyword.value,
feed=feed.key,
paths=keyword_matches,
Expand Down Expand Up @@ -59,5 +59,5 @@ async def run_crawler() -> list[dict]:

crawlers = [crawl_feed(feed, keywords) for feed in feeds]
# swallow exceptions to keep the loop running
results = await asyncio.gather(*crawlers, return_exceptions=True)
results = await asyncio.gather(*crawlers, return_exceptions=True) # type: ignore
return [entry for result in results if isinstance(result, list) for entry in result]
10 changes: 5 additions & 5 deletions backend/subabot/rss/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Dict, Optional
from typing import Any, Dict, Optional, Self

from pydantic import model_validator
from slugify import slugify
Expand All @@ -25,10 +25,10 @@ class Feed(SQLModel, DBMixin, table=True):
refreshed_at: Optional[int] = Field(default=None)

@model_validator(mode="before")
def populate_title(cls, values: Dict[str, Any]) -> Dict[str, Any]:
if "title" not in values:
values["title"] = values["key"]
return values
def populate_title(cls, obj: Self) -> Self:
if not obj.title:
obj.title = obj.key
return obj


class Crawl(SQLModel, DBMixin, table=True):
Expand Down
17 changes: 6 additions & 11 deletions backend/subabot/rss/router.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from fastapi import APIRouter, BackgroundTasks, Body
from fastapi import APIRouter, BackgroundTasks, Body, HTTPException
from fastapi.logger import logger
from sqlmodel import select

from subabot.config import APP_DIR
from subabot.db import SessionDep
from sqlmodel import select
from fastapi import HTTPException
from subabot.rss.crawler import crawl_feed
from subabot.rss.models import Feed, Keyword

Expand All @@ -14,20 +13,16 @@
# Feeds
@router.get("/feeds", response_model=list[Feed])
def read_feeds(session: SessionDep):
return session.exec(select(Feed)).all()
return Feed.list()


@router.post("/feeds", response_model=Feed)
def create_feed(feed: Feed, session: SessionDep, background_tasks: BackgroundTasks):
db_feed = Feed.model_validate(feed)
session.add(db_feed)
session.commit()
session.refresh(db_feed)

keywords = session.exec(select(Keyword)).all()
feed = Feed.upsert(session, **feed.model_fields)
keywords = list(Keyword.list())
background_tasks.add_task(crawl_feed, feed, keywords)

return db_feed
return feed


@router.delete("/feeds")
Expand Down
10 changes: 6 additions & 4 deletions backend/subabot/rss/utils.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
from typing import Any, Generator, Union
from typing import Any, Generator, Sequence, Union

from sqlmodel import select

from subabot.db import Session, engine
from subabot.rss.models import History


def find_matches(
data: Union[list[dict], dict, str],
data: Union[Sequence[dict], dict, str],
keyword: str,
pre_path: tuple = (),
) -> Generator[tuple, Any, Any]:
"""Generates tuples of paths to the keyword found in the data."""

if isinstance(data, list):
if isinstance(data, list) or isinstance(data, Sequence):
for index, item in enumerate(data):
path = pre_path + (str(index),)
yield from find_matches(item, keyword, path)
Expand All @@ -25,7 +27,7 @@ def find_matches(
yield pre_path


def get_matching_entries(entries: list[dict], matches: list[tuple]) -> list[dict]:
def get_matching_entries(entries: Sequence[dict], matches: list[tuple]) -> Sequence[dict]:
"""Returns a list of unique entries from the matches that are NOT in history."""

with Session(engine) as session:
Expand Down
Loading

0 comments on commit 18f4f88

Please sign in to comment.