Skip to content

Commit 6b5658a

Browse files
authored
feat: core logic svcs and schema (#93)
1 parent 81a61e6 commit 6b5658a

File tree

8 files changed

+587
-51
lines changed

8 files changed

+587
-51
lines changed

byte_bot/server/domain/guilds/dependencies.py

Lines changed: 130 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,156 @@
55
from typing import TYPE_CHECKING
66

77
from sqlalchemy import select
8+
from sqlalchemy.orm import joinedload, noload, selectinload
89

9-
from byte_bot.server.domain.db.models import Guild
10-
from byte_bot.server.domain.guilds.services import GuildsService
10+
from byte_bot.server.domain.db.models import AllowedUsersConfig, ForumConfig, GitHubConfig, Guild, SOTagsConfig
11+
from byte_bot.server.domain.guilds.services import (
12+
AllowedUsersConfigService,
13+
ForumConfigService,
14+
GitHubConfigService,
15+
GuildsService,
16+
SOTagsConfigService,
17+
)
1118
from byte_bot.server.lib import log
1219

1320
if TYPE_CHECKING:
1421
from collections.abc import AsyncGenerator
1522

1623
from sqlalchemy.ext.asyncio import AsyncSession
1724

18-
__all__ = ("provides_guilds_service",)
25+
__all__ = (
26+
"provides_allowed_users_config_service",
27+
"provides_forum_config_service",
28+
"provides_github_config_service",
29+
"provides_guilds_service",
30+
"provides_sotags_config_service",
31+
)
1932

2033
logger = log.get_logger()
2134

2235

2336
async def provides_guilds_service(db_session: AsyncSession) -> AsyncGenerator[GuildsService, None]:
24-
"""Construct GuildConfig-based repository and service objects for the request.
37+
"""Construct Guilds-based repository and service objects for the request.
2538
2639
Args:
2740
db_session (AsyncSession): SQLAlchemy AsyncSession
2841
2942
Yields:
3043
GuildsService: GuildConfig-based service
3144
"""
32-
async with GuildsService.new(session=db_session, statement=select(Guild).order_by(Guild.guild_id)) as service:
45+
async with GuildsService.new(
46+
session=db_session,
47+
statement=select(Guild)
48+
.order_by(Guild.guild_name)
49+
.options(
50+
selectinload(Guild.github_config).options(
51+
joinedload(GitHubConfig.guild, innerjoin=True).options(noload("*")),
52+
),
53+
selectinload(Guild.sotags_configs).options(
54+
joinedload(SOTagsConfig.guild, innerjoin=True).options(noload("*")),
55+
),
56+
selectinload(Guild.allowed_users).options(
57+
joinedload(AllowedUsersConfig.guild, innerjoin=True).options(noload("*")),
58+
),
59+
selectinload(Guild.forum_config).options(
60+
joinedload(ForumConfig.guild, innerjoin=True).options(noload("*")),
61+
),
62+
),
63+
) as service:
64+
try:
65+
yield service
66+
finally:
67+
...
68+
69+
70+
async def provides_github_config_service(db_session: AsyncSession) -> AsyncGenerator[GitHubConfigService, None]:
71+
"""Construct GitHubConfig-based repository and service objects for the request.
72+
73+
Args:
74+
db_session (AsyncSession): SQLAlchemy AsyncSession
75+
76+
Yields:
77+
GitHubConfigService: GitHubConfig-based service
78+
"""
79+
async with GitHubConfigService.new(
80+
session=db_session,
81+
statement=select(GitHubConfig)
82+
.order_by(GitHubConfig.github_organization)
83+
.options(
84+
selectinload(GitHubConfig.guild).options(noload("*")),
85+
),
86+
) as service:
87+
try:
88+
yield service
89+
finally:
90+
...
91+
92+
93+
async def provides_sotags_config_service(db_session: AsyncSession) -> AsyncGenerator[SOTagsConfigService, None]:
94+
"""Construct SOTagsConfig-based repository and service objects for the request.
95+
96+
Args:
97+
db_session (AsyncSession): SQLAlchemy AsyncSession
98+
99+
Yields:
100+
SOTagsConfigService: SOTagsConfig-based service
101+
"""
102+
async with SOTagsConfigService.new(
103+
session=db_session,
104+
statement=select(SOTagsConfig)
105+
.order_by(SOTagsConfig.tag_name)
106+
.options(
107+
selectinload(SOTagsConfig.guild).options(noload("*")),
108+
),
109+
) as service:
110+
try:
111+
yield service
112+
finally:
113+
...
114+
115+
116+
async def provides_allowed_users_config_service(
117+
db_session: AsyncSession,
118+
) -> AsyncGenerator[AllowedUsersConfigService, None]:
119+
"""Construct AllowedUsersConfig-based repository and service objects for the request.
120+
121+
Args:
122+
db_session (AsyncSession): SQLAlchemy AsyncSession
123+
124+
Yields:
125+
AllowedUsersConfigService: AllowedUsersConfig-based service
126+
"""
127+
async with AllowedUsersConfigService.new(
128+
session=db_session,
129+
statement=select(AllowedUsersConfig)
130+
.order_by(AllowedUsersConfig.user_id)
131+
.options(
132+
selectinload(AllowedUsersConfig.guild).options(noload("*")),
133+
),
134+
) as service:
135+
try:
136+
yield service
137+
finally:
138+
...
139+
140+
141+
async def provides_forum_config_service(db_session: AsyncSession) -> AsyncGenerator[ForumConfigService, None]:
142+
"""Construct ForumConfig-based repository and service objects for the request.
143+
144+
Args:
145+
db_session (AsyncSession): SQLAlchemy AsyncSession
146+
147+
Yields:
148+
ForumConfigService: ForumConfig-based service
149+
"""
150+
async with ForumConfigService.new(
151+
session=db_session,
152+
statement=select(ForumConfig)
153+
.order_by(ForumConfig.help_forum)
154+
.options(
155+
selectinload(ForumConfig.guild).options(noload("*")),
156+
),
157+
) as service:
33158
try:
34159
yield service
35160
finally:
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Helper functions to be used for interacting with Guild data."""
2+
3+
from __future__ import annotations
4+
5+
from byte_bot.server.domain.guilds.dependencies import provides_guilds_service
6+
from byte_bot.server.lib.db import config
7+
8+
__all__ = ("get_byte_server_count",)
9+
10+
11+
async def get_byte_server_count() -> int:
12+
"""Get the server count for Byte.
13+
14+
Returns:
15+
int: The server counts for Byte or 0 if there are none.
16+
"""
17+
async with config.get_session() as session:
18+
guilds_service = await anext(provides_guilds_service(db_session=session))
19+
total = await guilds_service.count()
20+
return total or 0

byte_bot/server/domain/guilds/schemas.py

Lines changed: 138 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,65 @@
22

33
from __future__ import annotations
44

5-
from typing import TYPE_CHECKING
6-
7-
if TYPE_CHECKING:
8-
from uuid import UUID
5+
from enum import StrEnum
6+
from uuid import UUID # noqa: TC003
97

108
from pydantic import Field
119

1210
from byte_bot.server.lib.schema import CamelizedBaseModel
11+
from byte_bot.server.lib.serialization import convert_camel_to_snake_case
12+
13+
__all__ = (
14+
"AllowedUsersConfigSchema",
15+
"ForumConfigSchema",
16+
"GitHubConfigSchema",
17+
"GuildCreate",
18+
"GuildSchema",
19+
"GuildUpdate",
20+
"SOTagsConfigSchema",
21+
"UpdateableGuildSetting",
22+
)
23+
24+
25+
class GitHubConfigSchema(CamelizedBaseModel):
26+
"""Schema for validating GitHub configuration."""
27+
28+
guild_id: UUID
29+
discussion_sync: bool
30+
github_organization: str | None
31+
github_repository: str | None
32+
33+
34+
class SOTagsConfigSchema(CamelizedBaseModel):
35+
"""Schema for validating StackOverflow tags configuration."""
36+
37+
guild_id: UUID
38+
tag_name: str
1339

14-
__all__ = ("GuildCreate", "GuildSchema", "GuildUpdate")
40+
41+
class AllowedUsersConfigSchema(CamelizedBaseModel):
42+
"""Schema for validating allowed users for certain admin actions within a guild."""
43+
44+
guild_id: UUID
45+
user_id: UUID
46+
47+
48+
class ForumConfigSchema(CamelizedBaseModel):
49+
"""Schema for validating forum configuration."""
50+
51+
guild_id: UUID
52+
help_forum: bool = Field(title="Help Forum", description="Is the help forum enabled.")
53+
help_forum_category: str
54+
help_thread_auto_close: bool
55+
help_thread_auto_close_days: int
56+
help_thread_notify: bool
57+
help_thread_notify_roles: list[int]
58+
help_thread_notify_days: int
59+
help_thread_sync: bool
60+
showcase_forum: bool
61+
showcase_forum_category: str
62+
showcase_thread_auto_close: bool
63+
showcase_thread_auto_close_days: int
1564

1665

1766
class GuildSchema(CamelizedBaseModel):
@@ -28,6 +77,18 @@ class GuildSchema(CamelizedBaseModel):
2877
issue_linking: bool | None = Field(title="Issue Linking", description="Is issue linking enabled.")
2978
comment_linking: bool | None = Field(title="Comment Linking", description="Is comment linking enabled.")
3079
pep_linking: bool | None = Field(title="PEP Linking", description="Is PEP linking enabled.")
80+
github_config: GitHubConfigSchema | None = Field(
81+
title="GitHub Config", description="The GitHub configuration for the guild."
82+
)
83+
sotags_configs: list[SOTagsConfigSchema] = Field(
84+
title="StackOverflow Tags Configs", description="The StackOverflow tags configuration for the guild."
85+
)
86+
allowed_users: list[AllowedUsersConfigSchema] = Field(
87+
title="Allowed Users", description="The allowed users configuration for the guild."
88+
)
89+
forum_config: ForumConfigSchema | None = Field(
90+
title="Forum Config", description="The forum configuration for the guild."
91+
)
3192

3293

3394
class GuildCreate(CamelizedBaseModel):
@@ -52,3 +113,75 @@ class GuildUpdate(CamelizedBaseModel):
52113
issue_linking: bool | None = Field(title="Issue Linking", description="Is issue linking enabled.")
53114
comment_linking: bool | None = Field(title="Comment Linking", description="Is comment linking enabled.")
54115
pep_linking: bool | None = Field(title="PEP Linking", description="Is PEP linking enabled.")
116+
117+
118+
class UpdateableGuildSetting(CamelizedBaseModel):
119+
"""Allowed settings that admins can update for their guild."""
120+
121+
"""Guild Model Settings"""
122+
prefix: str = Field(title="Prefix", description="The prefix for the guild.")
123+
help_channel_id: int = Field(title="Help Channel ID", description="The channel ID for the help forum.")
124+
showcase_channel_id: int = Field(title="Showcase Channel ID", description="The channel ID for the showcase forum.")
125+
sync_label: str = Field(title="Sync Label", description="The forum label to use for GitHub discussion syncs.")
126+
issue_linking: bool = Field(title="Issue Linking", description="Is issue linking enabled.")
127+
comment_linking: bool = Field(title="Comment Linking", description="Is comment linking enabled.")
128+
pep_linking: bool = Field(title="PEP Linking", description="Is PEP linking enabled.")
129+
130+
"""GitHub Config Settings"""
131+
discussion_sync: bool = Field(title="Discussion Sync", description="Is GitHub discussion sync enabled.")
132+
github_organization: str = Field(title="GitHub Organization", description="The GitHub organization to sync.")
133+
github_repository: str = Field(title="GitHub Repository", description="The GitHub repository to sync.")
134+
135+
"""StackOverflow Tags Config Settings"""
136+
tag_name: list[str] = Field(
137+
title="StackOverflow Tag(s)",
138+
description="The StackOverflow tag(s) to sync.",
139+
examples=["litestar", "byte", "python"],
140+
)
141+
142+
"""Allowed Users Config Settings"""
143+
allowed_user_id: int = Field(title="User ID", description="The user or role ID to allow.")
144+
145+
"""Forum Config Settings"""
146+
"""Help Forum"""
147+
help_forum: bool = Field(title="Help Forum", description="Is the help forum enabled.")
148+
help_forum_category: str = Field(title="Help Forum Category", description="The help forum category.")
149+
help_thread_auto_close: bool = Field(
150+
title="Help Thread Auto Close", description="Is the help thread auto close enabled."
151+
)
152+
help_thread_auto_close_days: int = Field(
153+
title="Help Thread Auto Close Days", description="The days to auto close help threads after inactivity."
154+
)
155+
help_thread_notify: bool = Field(
156+
title="Help Thread Notify", description="Whether to notify roles for unresponded help threads."
157+
)
158+
help_thread_notify_roles: list[int] = Field(
159+
title="Help Thread Notify Roles", description="The roles to notify for unresponded help threads."
160+
)
161+
help_thread_notify_days: int = Field(
162+
title="Help Thread Notify Days", description="The days to notify `notify_roles` after not receiving a response."
163+
)
164+
help_thread_sync: bool = Field(
165+
title="Help Thread Sync", description="Is the help thread GitHub discussions sync enabled."
166+
)
167+
168+
"""Showcase forum"""
169+
showcase_forum: bool = Field(title="Showcase Forum", description="Is the showcase forum enabled.")
170+
showcase_forum_category: str = Field(title="Showcase Forum Category", description="The showcase forum category.")
171+
showcase_thread_auto_close: bool = Field(
172+
title="Showcase Thread Auto Close", description="Is the showcase thread auto close enabled."
173+
)
174+
showcase_thread_auto_close_days: int = Field(
175+
title="Showcase Thread Auto Close Days", description="The days to auto close showcase threads after inactivity."
176+
)
177+
178+
@classmethod
179+
def as_enum(cls) -> type[StrEnum]:
180+
"""Helper to dynamically create an enum from the class fields."""
181+
enum_items = {
182+
convert_camel_to_snake_case(field.alias or field_name): convert_camel_to_snake_case(
183+
field.alias or field_name
184+
)
185+
for field_name, field in cls.model_fields.items()
186+
}
187+
return type(cls.__name__, (StrEnum,), enum_items)

0 commit comments

Comments
 (0)