Skip to content
This repository was archived by the owner on Nov 5, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion back/app/db/models/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion back/app/db/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ class User(Base):
back_populates="user",
cascade="all, delete-orphan",
passive_deletes=True,
lazy="selectin"
lazy="selectin",
)
11 changes: 7 additions & 4 deletions back/app/routes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

routers = []
providers = (
"about",
"auth",
"hello",
"workflow",
Expand All @@ -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}"
Expand Down
33 changes: 33 additions & 0 deletions back/app/routes/about.py
Original file line number Diff line number Diff line change
@@ -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
],
},
}
)
3 changes: 2 additions & 1 deletion back/app/routes/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand All @@ -73,6 +73,7 @@ async def get_me(
services=connected_services,
)


@router.patch(
"/credentials",
response_model=UserBase,
Expand Down
2 changes: 1 addition & 1 deletion back/app/routes/caldav/google.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)


Expand Down
2 changes: 1 addition & 1 deletion back/app/routes/discord/discord.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)


Expand Down
2 changes: 1 addition & 1 deletion back/app/routes/gmail/gmail.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)


Expand Down
10 changes: 3 additions & 7 deletions back/app/routes/oauth_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(".")
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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
2 changes: 1 addition & 1 deletion back/app/routes/spotify/spotify.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
)


Expand Down
3 changes: 2 additions & 1 deletion back/app/routes/youtube/youtube.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
28 changes: 18 additions & 10 deletions back/app/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down
17 changes: 17 additions & 0 deletions front/src/routes/not-found/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Link } from "react-router";
import "./style.scss";

export default function NotFoundPage() {
return (
<div className="notfound-page">
<div className="notfound-content">
<h1>404</h1>
<h2>Page Not Found</h2>
<p>Sorry, the page you are looking for does not exist.</p>
<Link to="/" className="btn">
Go Home
</Link>
</div>
</div>
);
}
30 changes: 30 additions & 0 deletions front/src/routes/not-found/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.notfound-page {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100vh;
text-align: center;
padding: 1em;
box-sizing: border-box;

.notfound-content {
h1 {
font-size: 6rem;
margin: 0;
color: #e4bef8;
}

h2 {
font-size: 2rem;
margin: 10px 0;
color: #c6d0f5;
}

p {
margin: 15px 0 25px;
color: #c6d0f5;
font-size: 1rem;
}
}
}
Loading