aboutsummaryrefslogtreecommitdiffstats
path: root/backend/models
diff options
context:
space:
mode:
authorGravatar Kieran Siek <[email protected]>2022-03-20 17:25:06 -0400
committerGravatar GitHub <[email protected]>2022-03-20 17:25:06 -0400
commit25fce5e0161c2d84d4a6b710aa5c83a863766f98 (patch)
treee3c15dad453f8d518bbf5335a14eddedf2c2d054 /backend/models
parentMerge pull request #151 from python-discord/dependabot/pip/sentry-sdk-1.5.7 (diff)
parentMerge branch 'main' into roles (diff)
Merge pull request #135 from python-discord/roles
Overhaul Access System
Diffstat (limited to 'backend/models')
-rw-r--r--backend/models/__init__.py5
-rw-r--r--backend/models/discord_role.py40
-rw-r--r--backend/models/discord_user.py34
-rw-r--r--backend/models/form.py13
4 files changed, 87 insertions, 5 deletions
diff --git a/backend/models/__init__.py b/backend/models/__init__.py
index 8ad7f7f..a9f76e0 100644
--- a/backend/models/__init__.py
+++ b/backend/models/__init__.py
@@ -1,12 +1,15 @@
from .antispam import AntiSpam
-from .discord_user import DiscordUser
+from .discord_role import DiscordRole
+from .discord_user import DiscordMember, DiscordUser
from .form import Form, FormList
from .form_response import FormResponse, ResponseList
from .question import CodeQuestion, Question
__all__ = [
"AntiSpam",
+ "DiscordRole",
"DiscordUser",
+ "DiscordMember",
"Form",
"FormResponse",
"CodeQuestion",
diff --git a/backend/models/discord_role.py b/backend/models/discord_role.py
new file mode 100644
index 0000000..c05c9de
--- /dev/null
+++ b/backend/models/discord_role.py
@@ -0,0 +1,40 @@
+import typing
+
+from pydantic import BaseModel
+
+
+class RoleTags(BaseModel):
+ """Meta information about a discord role."""
+
+ bot_id: typing.Optional[str]
+ integration_id: typing.Optional[str]
+ premium_subscriber: bool
+
+ def __init__(self, **data: typing.Any) -> None:
+ """
+ Handle the terrible discord API.
+
+ Discord only returns the premium_subscriber field if it's true,
+ meaning the typical validation process wouldn't work.
+
+ We manually parse the raw data to determine if the field exists, and give it a useful
+ bool value.
+ """
+ data["premium_subscriber"] = "premium_subscriber" in data.keys()
+ super().__init__(**data)
+
+
+class DiscordRole(BaseModel):
+ """Schema model of Discord guild roles."""
+
+ id: str
+ name: str
+ color: int
+ hoist: bool
+ icon: typing.Optional[str]
+ unicode_emoji: typing.Optional[str]
+ position: int
+ permissions: str
+ managed: bool
+ mentionable: bool
+ tags: typing.Optional[RoleTags]
diff --git a/backend/models/discord_user.py b/backend/models/discord_user.py
index 9f246ba..0eca15b 100644
--- a/backend/models/discord_user.py
+++ b/backend/models/discord_user.py
@@ -1,10 +1,11 @@
+import datetime
import typing as t
from pydantic import BaseModel
-class DiscordUser(BaseModel):
- """Schema model of Discord user for form response."""
+class _User(BaseModel):
+ """Base for discord users and members."""
# Discord default fields.
username: str
@@ -20,5 +21,34 @@ class DiscordUser(BaseModel):
premium_type: t.Optional[int]
public_flags: t.Optional[int]
+
+class DiscordUser(_User):
+ """Schema model of Discord user for form response."""
+
# Custom fields
admin: bool
+
+
+class DiscordMember(BaseModel):
+ """A discord guild member."""
+
+ user: _User
+ nick: t.Optional[str]
+ avatar: t.Optional[str]
+ roles: list[str]
+ joined_at: datetime.datetime
+ premium_since: t.Optional[datetime.datetime]
+ deaf: bool
+ mute: bool
+ pending: t.Optional[bool]
+ permissions: t.Optional[str]
+ communication_disabled_until: t.Optional[datetime.datetime]
+
+ def dict(self, *args, **kwargs) -> dict[str, t.Any]:
+ """Convert the model to a python dict, and encode timestamps in a serializable format."""
+ data = super().dict(*args, **kwargs)
+ for field, value in data.items():
+ if isinstance(value, datetime.datetime):
+ data[field] = value.isoformat()
+
+ return data
diff --git a/backend/models/form.py b/backend/models/form.py
index f19ed85..f888d6e 100644
--- a/backend/models/form.py
+++ b/backend/models/form.py
@@ -1,10 +1,10 @@
import typing as t
import httpx
-from pydantic import constr, BaseModel, Field, root_validator, validator
+from pydantic import BaseModel, Field, constr, root_validator, validator
from pydantic.error_wrappers import ErrorWrapper, ValidationError
-from backend.constants import FormFeatures, WebHook
+from backend.constants import DISCORD_GUILD, FormFeatures, WebHook
from .question import Question
PUBLIC_FIELDS = [
@@ -43,6 +43,8 @@ class Form(BaseModel):
submitted_text: t.Optional[str] = None
webhook: _WebHook = None
discord_role: t.Optional[str]
+ response_readers: t.Optional[list[str]]
+ editors: t.Optional[list[str]]
class Config:
allow_population_by_field_name = True
@@ -67,6 +69,13 @@ class Form(BaseModel):
return value
+ @validator("response_readers", "editors")
+ def validate_role_scoping(cls, value: t.Optional[list[str]]) -> t.Optional[list[str]]:
+ """Ensure special role based permissions aren't granted to the @everyone role."""
+ if value and str(DISCORD_GUILD) in value:
+ raise ValueError("You can not add the everyone role as an access scope.")
+ return value
+
@root_validator
def validate_role(cls, values: dict[str, t.Any]) -> t.Optional[dict[str, t.Any]]:
"""Validates does Discord role provided when flag provided."""