diff --git a/back/app/db/models/oauth.py b/back/app/db/models/oauth.py index 0faac11..1c1645c 100644 --- a/back/app/db/models/oauth.py +++ b/back/app/db/models/oauth.py @@ -12,7 +12,7 @@ class OAuthToken(Base): access_token = Column(String(128), nullable=False) refresh_token = Column(String(128), nullable=True) - service_id = Column(Integer, ForeignKey("service.id"), nullable=False) + service = Column(String, nullable=False) scope = Column(String(512), nullable=True) expires_at = Column(DateTime, nullable=True) diff --git a/back/app/db/models/user.py b/back/app/db/models/user.py index 741749d..ab092ca 100644 --- a/back/app/db/models/user.py +++ b/back/app/db/models/user.py @@ -30,5 +30,5 @@ class User(Base): back_populates="user", cascade="all, delete-orphan", passive_deletes=True, - lazy="selectin" + lazy="selectin", ) diff --git a/back/app/routes/__init__.py b/back/app/routes/__init__.py index dbfe8e8..b54e68d 100644 --- a/back/app/routes/__init__.py +++ b/back/app/routes/__init__.py @@ -4,6 +4,7 @@ routers = [] providers = ( + "about", "auth", "hello", "workflow", @@ -19,10 +20,12 @@ try: mod = importlib.import_module(f".{mod_name}", __package__) - assert hasattr(mod, "router"), \ - f"Module {mod.__name__} is missing 'router' attribute" - assert isinstance(mod.router, APIRouter), \ - f"'router' in module {mod.__name__} is not an APIRouter instance" + assert hasattr( + mod, "router" + ), f"Module {mod.__name__} is missing 'router' attribute" + assert isinstance( + mod.router, APIRouter + ), f"'router' in module {mod.__name__} is not an APIRouter instance" print( "Registering router from module:" f" {mod.__name__} with prefix: {mod.router.prefix}" diff --git a/back/app/routes/about.py b/back/app/routes/about.py new file mode 100644 index 0000000..f0cb029 --- /dev/null +++ b/back/app/routes/about.py @@ -0,0 +1,33 @@ +import time + +from fastapi import APIRouter, Request +from fastapi.responses import JSONResponse + +from .oauth_base import OAuthProvider + +router = APIRouter(prefix="") + + +@router.get("/about.json") +async def about_json(request: Request): + client = request.client + client_ip = client.host if client else "unknown" + services = OAuthProvider.services.keys() + return JSONResponse( + { + "client": { + "host": client_ip, + }, + "server": { + "current_time": int(time.time()), + "services": [ + { + "name": service_name, + "actions": [], + "reactions": [], + } + for service_name in services + ], + }, + } + ) diff --git a/back/app/routes/auth.py b/back/app/routes/auth.py index 6bdd8cb..162ba99 100644 --- a/back/app/routes/auth.py +++ b/back/app/routes/auth.py @@ -64,7 +64,7 @@ async def login_user( }, ) async def get_me( - current_user = Depends(get_current_user), + current_user=Depends(get_current_user), ) -> UserSchema: connected_services = {token.service: True for token in current_user.tokens} @@ -73,6 +73,7 @@ async def get_me( services=connected_services, ) + @router.patch( "/credentials", response_model=UserBase, diff --git a/back/app/routes/caldav/google.py b/back/app/routes/caldav/google.py index d24bc03..a427d11 100644 --- a/back/app/routes/caldav/google.py +++ b/back/app/routes/caldav/google.py @@ -47,7 +47,7 @@ class Config(BaseModel): provider = OAuthProvider( package=__package__, config_model=Config, - icon=(pathlib.Path(__file__).parent / "icon.svg").read_text() + icon=(pathlib.Path(__file__).parent / "icon.svg").read_text(), ) diff --git a/back/app/routes/discord/discord.py b/back/app/routes/discord/discord.py index 7bff75c..39df74c 100644 --- a/back/app/routes/discord/discord.py +++ b/back/app/routes/discord/discord.py @@ -33,7 +33,7 @@ class Config(BaseModel): provider = OAuthProvider( package=__package__, config_model=Config, - icon=(pathlib.Path(__file__).parent / "icon.svg").read_text() + icon=(pathlib.Path(__file__).parent / "icon.svg").read_text(), ) diff --git a/back/app/routes/gmail/gmail.py b/back/app/routes/gmail/gmail.py index 15ef556..b2eb919 100644 --- a/back/app/routes/gmail/gmail.py +++ b/back/app/routes/gmail/gmail.py @@ -49,7 +49,7 @@ class Config(BaseModel): provider = OAuthProvider( package=__package__, config_model=Config, - icon=(pathlib.Path(__file__).parent / "icon.svg").read_text() + icon=(pathlib.Path(__file__).parent / "icon.svg").read_text(), ) diff --git a/back/app/routes/oauth_base.py b/back/app/routes/oauth_base.py index 32b5639..cb12fc6 100644 --- a/back/app/routes/oauth_base.py +++ b/back/app/routes/oauth_base.py @@ -44,12 +44,7 @@ class OAuthProvider: services = {} - def __init__( - self, - icon: str, - package: str | None, - config_model: Any - ): + def __init__(self, icon: str, package: str | None, config_model: Any): assert package is not None, "Package name must be provided" *_, service_name = package.split(".") @@ -172,7 +167,7 @@ async def auth(self, code: str, state: str, db: AsyncSession): tokens = resp.json() token = OAuthToken( - user_id=user.id, + owner_id=user.id, service=self.cfg.service, access_token=tokens.get("access_token"), refresh_token=tokens.get("refresh_token"), @@ -294,6 +289,7 @@ async def me(self, user: User, db: AsyncSession): router = APIRouter(prefix="/services", tags=["services"]) + @router.get("", response_model=dict[str, str]) async def get_service_list(): return OAuthProvider.services diff --git a/back/app/routes/spotify/spotify.py b/back/app/routes/spotify/spotify.py index 5e79779..3b30e25 100644 --- a/back/app/routes/spotify/spotify.py +++ b/back/app/routes/spotify/spotify.py @@ -29,7 +29,7 @@ class Config(BaseModel): provider = OAuthProvider( package=__package__, config_model=Config, - icon=(pathlib.Path(__file__).parent / "icon.svg").read_text() + icon=(pathlib.Path(__file__).parent / "icon.svg").read_text(), ) diff --git a/back/app/routes/youtube/youtube.py b/back/app/routes/youtube/youtube.py index 3ec23b1..a0d6256 100644 --- a/back/app/routes/youtube/youtube.py +++ b/back/app/routes/youtube/youtube.py @@ -40,9 +40,10 @@ class Config(BaseModel): provider = OAuthProvider( package=__package__, config_model=Config, - icon=(pathlib.Path(__file__).parent / "icon.svg").read_text() + icon=(pathlib.Path(__file__).parent / "icon.svg").read_text(), ) + @router.get("/connect") async def youtube_connect(token: str = Query(...), platform: str = Query(...)): return await provider.connect(token, platform) diff --git a/back/app/service.py b/back/app/service.py index dc93680..a3e64d2 100644 --- a/back/app/service.py +++ b/back/app/service.py @@ -10,24 +10,32 @@ def __init__(self, name: str, description: str): def action(self, name: str, description: str): """Decorator to register an action with metadata.""" + def wrapper(func: Callable): - self.actions.append({ - "name": name, - "description": description, - "function": func, - }) + self.actions.append( + { + "name": name, + "description": description, + "function": func, + } + ) return func + return wrapper def reaction(self, name: str, description: str): """Decorator to register a reaction with metadata.""" + def wrapper(func: Callable): - self.reactions.append({ - "name": name, - "description": description, - "function": func, - }) + self.reactions.append( + { + "name": name, + "description": description, + "function": func, + } + ) return func + return wrapper def to_dict(self) -> dict[str, Any]: diff --git a/front/src/routes/not-found/index.tsx b/front/src/routes/not-found/index.tsx new file mode 100644 index 0000000..5ac0a9d --- /dev/null +++ b/front/src/routes/not-found/index.tsx @@ -0,0 +1,17 @@ +import { Link } from "react-router"; +import "./style.scss"; + +export default function NotFoundPage() { + return ( +
Sorry, the page you are looking for does not exist.
+ + Go Home + +{workflow.description || "No description"}
+ {editingId === workflow.id ? ( + <> + + setEditingData((d) => ({ ...d, name: e.target.value })) + } + /> + + setEditingData((d) => ({ + ...d, + description: e.target.value, + })) + } + /> + > + ) : ( + <> +{workflow.description || "No description"}
+ > + )}