Skip to content

Commit 0270c0d

Browse files
authored
feat: add comprehensive guild API controllers and system endpoints (#94)
1 parent 6b5658a commit 0270c0d

File tree

5 files changed

+273
-23
lines changed

5 files changed

+273
-23
lines changed

byte_bot/server/domain/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
routes: list[ControllerRouterHandler] = [
2828
system.controllers.system.SystemController,
2929
web.controllers.web.WebController,
30-
guilds.controllers.GuildController,
30+
guilds.controllers.GuildsController,
3131
]
3232
"""Routes for the application."""
3333

byte_bot/server/domain/guilds/controllers.py

Lines changed: 204 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,52 @@
44

55
from typing import TYPE_CHECKING
66

7-
from litestar import Controller, get, post
7+
from litestar import Controller, get, patch, post
88
from litestar.di import Provide
99
from litestar.params import Dependency, Parameter
1010

11-
from byte_bot.server.domain import urls
12-
from byte_bot.server.domain.guilds.dependencies import provides_guilds_service
13-
from byte_bot.server.domain.guilds.schemas import GuildSchema
14-
from byte_bot.server.domain.guilds.services import GuildsService # noqa: TC001
11+
from byte_bot.server.domain.guilds import urls
12+
from byte_bot.server.domain.guilds.dependencies import (
13+
provides_allowed_users_config_service,
14+
provides_forum_config_service,
15+
provides_github_config_service,
16+
provides_guilds_service,
17+
provides_sotags_config_service,
18+
)
19+
from byte_bot.server.domain.guilds.schemas import (
20+
AllowedUsersConfigSchema,
21+
ForumConfigSchema,
22+
GitHubConfigSchema,
23+
GuildSchema,
24+
SOTagsConfigSchema,
25+
UpdateableGuildSettingEnum,
26+
)
27+
from byte_bot.server.domain.guilds.services import (
28+
AllowedUsersConfigService, # noqa: TC001
29+
ForumConfigService, # noqa: TC001
30+
GitHubConfigService, # noqa: TC001
31+
GuildsService, # noqa: TC001
32+
SOTagsConfigService, # noqa: TC001
33+
)
1534

1635
if TYPE_CHECKING:
1736
from advanced_alchemy.filters import FilterTypes
1837
from advanced_alchemy.service import OffsetPagination
1938

20-
__all__ = ("GuildController",)
39+
__all__ = ("GuildsController",)
2140

2241

23-
class GuildController(Controller):
42+
class GuildsController(Controller):
2443
"""Controller for guild-based routes."""
2544

2645
tags = ["Guilds"]
27-
dependencies = {"guilds_service": Provide(provides_guilds_service)}
46+
dependencies = {
47+
"guilds_service": Provide(provides_guilds_service),
48+
"github_service": Provide(provides_github_config_service),
49+
"sotags_service": Provide(provides_sotags_config_service),
50+
"allowed_users_service": Provide(provides_allowed_users_config_service),
51+
"forum_service": Provide(provides_forum_config_service),
52+
}
2853

2954
@get(
3055
operation_id="Guilds",
@@ -80,3 +105,174 @@ async def create_guild(
80105
new_guild = {"guild_id": guild_id, "guild_name": guild_name}
81106
await guilds_service.create(new_guild)
82107
return f"Guild {guild_name} created."
108+
109+
@patch(
110+
operation_id="UpdateGuild",
111+
name="guilds:update",
112+
summary="Update a guild.",
113+
path=urls.GUILD_UPDATE,
114+
)
115+
async def update_guild(
116+
self,
117+
guilds_service: GuildsService,
118+
guild_id: int = Parameter(
119+
title="Guild ID",
120+
description="The guild ID.",
121+
),
122+
setting: UpdateableGuildSettingEnum = Parameter(
123+
title="Setting",
124+
description="The setting to update.",
125+
),
126+
value: str | int = Parameter(
127+
title="Value",
128+
description="The new value for the setting.",
129+
),
130+
) -> GuildSchema | OffsetPagination[GuildSchema]:
131+
"""Update a guild by ID.
132+
133+
Args:
134+
guilds_service (GuildsService): Guilds service
135+
guild_id (Guild.guild_id): Guild ID
136+
setting (UpdateableGuildSettingEnum): Setting to update
137+
value (str | int): New value for the setting
138+
139+
Returns:
140+
Guild: Updated guild object
141+
"""
142+
result = await guilds_service.get(guild_id, id_attribute="guild_id")
143+
# todo: this is a placeholder, update to grab whichever setting is being update, and update the corresponding
144+
# tables value based on the setting parameter
145+
await guilds_service.update({str(setting): value}, item_id=guild_id)
146+
return guilds_service.to_schema(schema_type=GuildSchema, data=result)
147+
148+
@get(
149+
operation_id="GuildDetail",
150+
name="guilds:detail",
151+
summary="Get guild details.",
152+
path=urls.GUILD_DETAIL,
153+
)
154+
async def get_guild(
155+
self,
156+
guilds_service: GuildsService,
157+
guild_id: int = Parameter(
158+
title="Guild ID",
159+
description="The guild ID.",
160+
),
161+
) -> GuildSchema:
162+
"""Get a guild by ID.
163+
164+
Args:
165+
guilds_service (GuildsService): Guilds service
166+
guild_id (int): Guild ID
167+
168+
Returns:
169+
Guild: Guild object
170+
"""
171+
result = await guilds_service.get(guild_id, id_attribute="guild_id")
172+
return guilds_service.to_schema(schema_type=GuildSchema, data=result)
173+
174+
@get(
175+
operation_id="GitHubDetail",
176+
name="guilds:github-config",
177+
summary="Get GitHub config for a guild.",
178+
path=urls.GUILD_GITHUB_DETAIL,
179+
)
180+
async def get_guild_github_config(
181+
self,
182+
github_service: GitHubConfigService,
183+
guild_id: int = Parameter(
184+
title="Guild ID",
185+
description="The guild ID.",
186+
),
187+
) -> GitHubConfigSchema | OffsetPagination[GitHubConfigSchema]:
188+
"""Get a guild's GitHub config by ID.
189+
190+
TODO(#88): a helper method that we can use outside of routes would be nice.
191+
192+
Args:
193+
github_service (GitHubConfigService): GitHub config service
194+
guild_id (int): Guild ID
195+
196+
Returns:
197+
GitHubConfig: GitHub config object
198+
"""
199+
result = await github_service.get(guild_id, id_attribute="guild_id")
200+
return github_service.to_schema(schema_type=GitHubConfigSchema, data=result)
201+
202+
@get(
203+
operation_id="SOTagsDetail",
204+
name="guilds:sotags-config",
205+
summary="Get StackOverflow tags config for a guild.",
206+
path=urls.GUILD_SOTAGS_DETAIL,
207+
)
208+
async def get_guild_sotags_config(
209+
self,
210+
sotags_service: SOTagsConfigService,
211+
guild_id: int = Parameter(
212+
title="Guild ID",
213+
description="The guild ID.",
214+
),
215+
) -> SOTagsConfigSchema | OffsetPagination[SOTagsConfigSchema]:
216+
"""Get a guild's StackOverflow tags config by ID.
217+
218+
Args:
219+
sotags_service (SOTagsConfigService): StackOverflow tags config service
220+
guild_id (int): Guild ID
221+
222+
Returns:
223+
SOTagsConfig: StackOverflow tags config object
224+
"""
225+
result = await sotags_service.get(guild_id, id_attribute="guild_id")
226+
return sotags_service.to_schema(schema_type=SOTagsConfigSchema, data=result)
227+
228+
@get(
229+
operation_id="AllowedUsersDetail",
230+
name="guilds:allowed-users-config",
231+
summary="Get allowed users config for a guild.",
232+
path=urls.GUILD_ALLOWED_USERS_DETAIL,
233+
)
234+
async def get_guild_allowed_users_config(
235+
self,
236+
allowed_users_service: AllowedUsersConfigService,
237+
guild_id: int = Parameter(
238+
title="Guild ID",
239+
description="The guild ID.",
240+
),
241+
) -> AllowedUsersConfigSchema | OffsetPagination[AllowedUsersConfigSchema]:
242+
"""Get a guild's allowed users config by ID.
243+
244+
Args:
245+
allowed_users_service (AllowedUsersConfigService): Allowed users config service
246+
guild_id (int): Guild ID
247+
248+
Returns:
249+
AllowedUsersConfig: Allowed users config object
250+
"""
251+
result = await allowed_users_service.get(guild_id, id_attribute="guild_id")
252+
return allowed_users_service.to_schema(schema_type=AllowedUsersConfigSchema, data=result)
253+
254+
@get(
255+
operation_id="ForumDetail",
256+
name="guilds:forum-config",
257+
summary="Get forum config for a guild.",
258+
path=urls.GUILD_FORUM_DETAIL,
259+
)
260+
async def get_guild_forum_config(
261+
self,
262+
forum_service: ForumConfigService,
263+
guild_id: int = Parameter(
264+
title="Guild ID",
265+
description="The guild ID.",
266+
),
267+
) -> ForumConfigSchema | OffsetPagination[ForumConfigSchema]:
268+
"""Get a guild's forum config by ID.
269+
270+
Args:
271+
forum_service (ForumConfigService): Forum config service
272+
guild_id (int): Guild ID
273+
274+
Returns:
275+
ForumConfig: Forum config object
276+
"""
277+
result = await forum_service.get(guild_id, id_attribute="guild_id")
278+
return forum_service.to_schema(schema_type=ForumConfigSchema, data=result)

byte_bot/server/domain/guilds/schemas.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
from enum import StrEnum
6+
from typing import TYPE_CHECKING, cast
67
from uuid import UUID # noqa: TC003
78

89
from pydantic import Field
@@ -19,6 +20,7 @@
1920
"GuildUpdate",
2021
"SOTagsConfigSchema",
2122
"UpdateableGuildSetting",
23+
"UpdateableGuildSettingEnum",
2224
)
2325

2426

@@ -184,4 +186,11 @@ def as_enum(cls) -> type[StrEnum]:
184186
)
185187
for field_name, field in cls.model_fields.items()
186188
}
187-
return type(cls.__name__, (StrEnum,), enum_items)
189+
return cast("type[StrEnum]", type(cls.__name__, (StrEnum,), enum_items))
190+
191+
192+
# what the fuck
193+
if TYPE_CHECKING:
194+
UpdateableGuildSettingEnum = type[StrEnum]
195+
else:
196+
UpdateableGuildSettingEnum = UpdateableGuildSetting.as_enum()

byte_bot/server/domain/system/controllers/system.py

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,70 @@
22

33
from __future__ import annotations
44

5-
from litestar import Controller
5+
from typing import TYPE_CHECKING
6+
7+
from litestar import Controller, MediaType, get
8+
from litestar.response import Response
9+
10+
from byte_bot.server.domain import urls
11+
from byte_bot.server.domain.system.dtos import SystemHealth
12+
from byte_bot.server.domain.system.helpers import check_byte_status, check_database_status
13+
from byte_bot.server.lib import log
14+
15+
if TYPE_CHECKING:
16+
from sqlalchemy.ext.asyncio import AsyncSession
617

718
__all__ = ["SystemController"]
819

20+
logger = log.get_logger()
21+
922

1023
class SystemController(Controller):
1124
"""System Controller."""
1225

1326
opt = {"exclude_from_auth": True}
1427

15-
# TODO: Implement:
16-
# - /health endpoint
28+
@get(
29+
operation_id="SystemHealth",
30+
name="system:health",
31+
path=urls.SYSTEM_HEALTH,
32+
media_type=MediaType.JSON,
33+
cache=False,
34+
tags=["System"],
35+
summary="Health Check",
36+
description="Execute a health check against backend components including database and bot status.",
37+
signature_namespace={"SystemHealth": SystemHealth},
38+
)
39+
async def check_system_health(self, db_session: AsyncSession) -> Response[SystemHealth]:
40+
"""Check the overall system health.
41+
42+
Args:
43+
db_session (AsyncSession): Database session.
44+
45+
Returns:
46+
Response[SystemHealth]: System health.
47+
"""
48+
database_status = await check_database_status(db_session)
49+
byte_status = await check_byte_status()
50+
statuses = [database_status, byte_status]
51+
52+
if all(status == "offline" for status in statuses):
53+
overall_status = "offline"
54+
elif "offline" in statuses or "degraded" in statuses:
55+
overall_status = "degraded"
56+
else:
57+
overall_status = "healthy"
58+
59+
status_code = 200 if overall_status == "healthy" else 503 if overall_status == "degraded" else 500
60+
# noinspection PyTypeChecker
61+
system_health_detail = SystemHealth(
62+
database_status=database_status,
63+
byte_status=byte_status,
64+
overall_status=overall_status,
65+
)
66+
67+
return Response(
68+
content=system_health_detail,
69+
status_code=status_code,
70+
media_type=MediaType.JSON,
71+
)

byte_bot/server/domain/urls.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,3 @@
1818
# --- Bot
1919

2020
# --- Reports
21-
22-
# --- API
23-
GUILD_CREATE: Final = f"{OPENAPI_SCHEMA}/guilds/create"
24-
"""Create guild URL."""
25-
GUILD_UPDATE: Final = f"{OPENAPI_SCHEMA}/guilds/update"
26-
"""Update guild URL."""
27-
GUILD_DETAIL: Final = f"{OPENAPI_SCHEMA}/guilds/{{guild_id}}"
28-
"""Guild detail URL."""
29-
GUILD_LIST: Final = f"{OPENAPI_SCHEMA}/guilds/list"
30-
"""Guild list URL."""

0 commit comments

Comments
 (0)