Skip to content

Commit

Permalink
chore: bring back useful changes from relay.md
Browse files Browse the repository at this point in the history
  • Loading branch information
xeroc committed May 2, 2024
1 parent 1c21f84 commit 832c927
Show file tree
Hide file tree
Showing 47 changed files with 4,363 additions and 372 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ repos:
rev: v4.2.0
hooks:
- id: trailing-whitespace
exclude: ".pug$"
- id: check-docstring-first
- id: check-executables-have-shebangs
- id: check-json
Expand Down
7 changes: 4 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,10 @@ docker_publish:
.PHONY: release
release:
git diff-index --quiet HEAD || { echo "untracked files! Aborting"; exit 1; }
git checkout develop
git checkout -b release/$(shell date +'%Y%m%d')
git push origin release/$(shell date +'%Y%m%d')
git checkout relay.md/develop
git checkout -b release/$(shell date +'%Y%m%d%H%m')
git push origin release/$(shell date +'%Y%m%d%H%m')
git checkout relay.md/develop

.PHONY: db_upgrade
db_update:
Expand Down
2 changes: 2 additions & 0 deletions backend/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .routes.v1 import docs as v1_docs
from .routes.v1 import teams as v1_teams
from .routes.v1 import topics as v1_topics
from .routes.v1 import user as v1_user

# Setup sentry for alerting in case of exceptions
if get_config().SENTRY_DSN:
Expand All @@ -38,6 +39,7 @@
app.include_router(v1_assets.router)
app.include_router(v1_teams.router)
app.include_router(v1_topics.router)
app.include_router(v1_user.router)

# Set all CORS enabled origins
app.add_middleware(
Expand Down
2 changes: 2 additions & 0 deletions backend/models/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,7 @@ class Asset(Base):
filesize: Mapped[int] = mapped_column(BigInteger())
checksum_sha256: Mapped[str] = mapped_column(String(length=64), nullable=True)

deleted_at: Mapped[datetime] = mapped_column(nullable=True)

def __repr__(self):
return f"<{self.__class__.__name__}: {self.filename}>"
2 changes: 2 additions & 0 deletions backend/models/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class Document(Base):
# 128bit password hash, as bytes TODO: needs implementation
read_password_hash: Mapped[bytes] = mapped_column(CHAR(32), default=b"")

deleted_at: Mapped[datetime] = mapped_column(nullable=True)

def __repr__(self):
return f"<{self.__class__.__name__}@{self.user.username}>"

Expand Down
19 changes: 17 additions & 2 deletions backend/repos/user_team.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,32 @@
from typing import Optional
from uuid import UUID

from sqlalchemy import and_

from ..models.user import User
from ..models.team import Team
from ..models.user_team import UserTeam
from ..models.team_topic import TeamTopic
from ..models.user_team_topic import UserTeamTopic
from .base import DatabaseAbstractRepository


class UserTeamRepo(DatabaseAbstractRepository):
ORM_Model = UserTeam

def add_member(self, user_id: UUID, team_id: UUID):
self.create_from_kwargs(user_id=user_id, team_id=team_id)
def add_member(self, user: User, team: Team):
self.create_from_kwargs(user_id=user.id, team_id=team.id)

def remove_member(self, membership):
# also delete all subscriptions to team topics
self._db.query(UserTeamTopic).filter(
and_(
UserTeamTopic.user_id == membership.user_id,
TeamTopic.team_id == membership.team_id,
UserTeamTopic.team_topic_id == TeamTopic.id,
)
).delete(synchronize_session=False)

self.delete(membership)

def ensure_member(self, user_id: UUID, team_id: UUID) -> Optional[object]:
Expand Down
13 changes: 9 additions & 4 deletions backend/repos/user_team_topic.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,21 @@ class UserTeamTopicRepo(DatabaseAbstractRepository):
ORM_Model = UserTeamTopic

def create_from_kwargs(self, **kwargs):
user_team_topic = super().create_from_kwargs(**kwargs)

team_topic_repo = TeamTopicRepo(self._db)
team_topic = team_topic_repo.get_by_id(user_team_topic.team_topic_id)
team_topic = team_topic_repo.get_by_id(kwargs["team_topic_id"])

if not team_topic:
raise ValueError("Invalid team_topic id!")

# Let's make sure we are subscribed to the team when we subscribe to a
# topic!
# WARNING: This may raise. Then the user cannot subscribe to the topic!
user_team_repo = UserTeamRepo(self._db)
user_team_repo.ensure_member(
user_id=user_team_topic.user_id, team_id=team_topic.team_id
user_id=kwargs["user_id"], team_id=team_topic.team_id
)

# Create subscription to topic here
user_team_topic = super().create_from_kwargs(**kwargs)

return user_team_topic
2 changes: 2 additions & 0 deletions backend/routes/login/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ async def onboarding_github_post(
user = user_repo.create_from_kwargs(
username=username,
email=email.lower(),
firstname=first_name,
lastname=last_name,
name=f"{first_name} {last_name}",
oauth_provider=OauthProvider.GITHUB,
profile_picture_url=github_user["avatar_url"],
Expand Down
64 changes: 23 additions & 41 deletions backend/routes/team.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@
from ..database import Session, get_session
from ..models.permissions import Permissions
from ..repos.team import Team, TeamRepo
from ..repos.team_topic import TeamTopicRepo
from ..repos.user import UserRepo
from ..repos.user_team import UserTeamRepo
from ..templates import templates
from ..utils.team import get_team
from ..utils.user import User, require_user
from ..utils.user import User, get_optional_user, require_user

router = APIRouter(prefix="/team")

Expand Down Expand Up @@ -44,10 +43,10 @@ async def join(
user: User = Depends(require_user),
db: Session = Depends(get_session),
):
repo = UserTeamRepo(db)
user_team_repo = UserTeamRepo(db)
if not team.can(Permissions.can_join, user):
raise exceptions.NotAllowed(f"You are not allowed to join team {team_name}!")
repo.add_member(user_id=user.id, team_id=team.id)
user_team_repo.add_member(user=user, team=team)
return RedirectResponse(url=request.url_for("show_team", team_name=team_name))


Expand Down Expand Up @@ -84,11 +83,9 @@ async def invite_user(
f"You are not allowed to invite to team {team_name}!"
)
user_team_repo = UserTeamRepo(db)
if user_team_repo.count(team_id=team.id) >= team.seats:
raise exceptions.NotAllowed("No seats left.")
# only add if not already added
if not user_team_repo.get_by_kwargs(user_id=new_user.id, team_id=team.id):
user_team_repo.add_member(user_id=new_user.id, team_id=team.id)
user_team_repo.add_member(user=new_user, team=team)
return RedirectResponse(url=request.url_for("settings", team_name=team_name))


Expand Down Expand Up @@ -237,7 +234,7 @@ def user_invite_link(user, team):
async def team_create_validate_team_name(
request: Request,
team_name: str = Form(default=""),
user: User = Depends(require_user),
user: User = Depends(get_optional_user),
db: Session = Depends(get_session),
):
team_repo = TeamRepo(db)
Expand Down Expand Up @@ -304,48 +301,33 @@ async def update_team_hide(
return """<p id="validate-team-name" class="help is-success">Team updated!</p>"""


@router.post("/{team_name}/topic/create", response_class=PlainTextResponse)
async def create_topic_htx(
@router.post("/{team_name}/seats", response_class=PlainTextResponse)
async def update_team_seats(
request: Request,
topic: str = Form(default=False),
seats: int = Form(default=0),
team: Team = Depends(get_team),
user: User = Depends(require_user),
db: Session = Depends(get_session),
):
TeamRepo(db)
if not team.user_id == user.id:
return """<p id="validate-team-name" class="help is-danger">You cannot update the headline!</p>"""
team_topic_repo = TeamTopicRepo(db)
try:
team_topic_repo.from_string(f"{topic}@{team.name}", user)
except exceptions.BaseAPIException as exc:
return f"""<p id="validate-team-name" class="help is-danger">{exc}!</p>"""
return """<p id="validate-team-name" class="help is-success">Topic Created!</p>"""
raise NotImplementedError("This endpoint is unavailable in open source version")


@router.get("/{team_name}/api/topic/list")
async def api_list_topics_in_team(
@router.get("/{team_name}/billing")
async def team_billing(
request: Request,
team_name: str,
team: Team = Depends(get_team),
user: User = Depends(require_user),
db: Session = Depends(get_session),
):
user_repo = UserRepo(db)
topics = user_repo.get_subscriptions(user=user, team=team)
ret = list()
for topic_with_subscription in topics:
topic = topic_with_subscription[0]
subscribed = topic_with_subscription[1]
if subscribed:
toggle_link = request.url_for("unsubscribe", team_topic_name=topic.name)
else:
toggle_link = request.url_for("subscribe", team_topic_name=topic.name)
ret.append(
dict(
name=topic.name,
id=topic.id,
subscribed=subscribed,
toggle_url=str(toggle_link),
)
)
return ret
raise NotImplementedError("This endpoint is unavailable in open source version")


@router.get("/{team_name}/billing/subscription/cancel")
async def cancel_subscription(
request: Request,
team: Team = Depends(get_team),
user: User = Depends(require_user),
db: Session = Depends(get_session),
):
raise NotImplementedError("This endpoint is unavailable in open source version")
86 changes: 73 additions & 13 deletions backend/routes/topic.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,45 @@
# -*- coding: utf-8 -*-


from fastapi import APIRouter, Depends, Request
from starlette.responses import RedirectResponse
from fastapi import APIRouter, Depends, Form, Query, Request
from fastapi.responses import JSONResponse

from .. import exceptions
from ..database import Session, get_session
from ..models.permissions import Permissions
from ..repos.team import Team
from ..repos.team_topic import TeamTopicRepo
from ..repos.user import UserRepo
from ..repos.user_team_topic import UserTeamTopicRepo
from ..utils.team import get_team_topic
from ..utils.team import get_team, get_team_topic
from ..utils.user import User, require_user

router = APIRouter(prefix="/topic")


@router.get("/{team_topic_name}/subscribe")
@router.post("/{team_topic_name}/subscribe")
async def subscribe(
team_topic_name: str,
request: Request,
team_topic: Team = Depends(get_team_topic),
user: User = Depends(require_user),
db: Session = Depends(get_session),
):
user_team_repo = UserTeamTopicRepo(db)
user_team_topic_repo = UserTeamTopicRepo(db)
user_repo = UserRepo(db)
membership = user_repo.is_member(user, team_topic.team)
# TODO: maybe we should introduce another permission here
if not team_topic.team.can(Permissions.can_read, user, membership):
raise exceptions.NotAllowed(f"Team {team_topic.team.name} is private!")
user_team_repo.create_from_kwargs(user_id=user.id, team_topic_id=team_topic.id)
return RedirectResponse(
url=request.url_for("show_team", team_name=team_topic.team.name)
return "not allowed"
user_team_topic_repo.create_from_kwargs(
user_id=user.id, team_topic_id=team_topic.id
)
response = JSONResponse(dict(status="ok"))
response.headers["HX-Trigger"] = "refresh-topics"
return response


@router.get("/{team_topic_name}/unsubscribe")
@router.post("/{team_topic_name}/unsubscribe")
async def unsubscribe(
team_topic_name: str,
request: Request,
Expand All @@ -49,6 +52,63 @@ async def unsubscribe(
user_id=user.id, team_topic_id=team_topic.id
)
user_team_repo.delete(user_team_topic)
return RedirectResponse(
url=request.url_for("show_team", team_name=team_topic.team.name)
)
response = JSONResponse(dict(status="ok"))
response.headers["HX-Trigger"] = "refresh-topics"
return response


@router.get("/{team_name}/api/topic/list")
async def api_list_topics_in_team(
request: Request,
search: str = Query(""),
team: Team = Depends(get_team),
user: User = Depends(require_user),
db: Session = Depends(get_session),
):
user_repo = UserRepo(db)
topics = user_repo.get_subscriptions(user=user, team=team)
ret = list()
for topic_with_subscription in topics:
topic = topic_with_subscription[0]
if search and search not in topic.name:
continue
subscribed = topic_with_subscription[1]
if subscribed:
toggle_link = request.url_for("unsubscribe", team_topic_name=topic.name)
else:
toggle_link = request.url_for("subscribe", team_topic_name=topic.name)
ret.append(
dict(
name=topic.name,
id=topic.id,
subscribed=subscribed,
toggle_url=str(toggle_link),
)
)
return ret


@router.post("/{team_name}/topic/create")
async def create_topic_htx(
topic: str = Form(default=False),
team: Team = Depends(get_team),
user: User = Depends(require_user),
db: Session = Depends(get_session),
):
if not team.user_id == user.id:
return """<p id="validate-team-name" class="help is-danger">You cannot update the headline!</p>"""
team_topic_repo = TeamTopicRepo(db)
user_team_topic_repo = UserTeamTopicRepo(db)
try:
team_topic = team_topic_repo.from_string(f"{topic}@{team.name}", user)
response = JSONResponse(dict(status="ok"))
try:
user_team_topic_repo.create_from_kwargs(
user_id=user.id, team_topic_id=team_topic.id
)
except Exception:
pass
except exceptions.BaseAPIException as exc:
response = JSONResponse(dict(error=str(exc)))
response.headers["HX-Trigger"] = "refresh-topics"
return response
2 changes: 1 addition & 1 deletion backend/routes/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

async def get_access_token(
api_key_header: str = Security(api_key_header), db: Session = Depends(get_session)
) -> str:
) -> AccessToken:
try:
api_key_uuid = UUID(api_key_header)
access_token_repo = AccessTokenRepo(db)
Expand Down
Loading

0 comments on commit 832c927

Please sign in to comment.