Skip to content

Commit 82abf8f

Browse files
authored
feat(backend): support update site_setting (#9)
1 parent 65c9337 commit 82abf8f

4 files changed

Lines changed: 131 additions & 12 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""empty message
2+
3+
Revision ID: 2fc10c21bf88
4+
Revises: 8248a83756fd
5+
Create Date: 2024-07-07 06:14:43.743558
6+
7+
"""
8+
9+
from alembic import op
10+
import sqlalchemy as sa
11+
import sqlmodel.sql.sqltypes
12+
from tidb_vector.sqlalchemy import VectorType
13+
14+
15+
# revision identifiers, used by Alembic.
16+
revision = "2fc10c21bf88"
17+
down_revision = "8248a83756fd"
18+
branch_labels = None
19+
depends_on = None
20+
21+
22+
def upgrade():
23+
# ### commands auto generated by Alembic - please adjust! ###
24+
op.create_unique_constraint(None, "site_settings", ["name"])
25+
# ### end Alembic commands ###
26+
27+
28+
def downgrade():
29+
# ### commands auto generated by Alembic - please adjust! ###
30+
op.drop_constraint(None, "site_settings", type_="unique")
31+
# ### end Alembic commands ###
Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,66 @@
1-
from typing import Dict
1+
from typing import Dict, Union
22
from pydantic import BaseModel
3-
from fastapi import APIRouter
3+
from http import HTTPStatus
4+
from fastapi import APIRouter, HTTPException
5+
from fastapi.exceptions import RequestValidationError
46

5-
from app.api.deps import CurrentSuperuserDep
6-
from app.site_settings import SiteSetting, SettingValue
7+
from app.api.deps import CurrentSuperuserDep, SessionDep
8+
from app.site_settings import SiteSetting, SettingValue, SettingType
79

810
router = APIRouter()
911

1012

1113
@router.get("/admin/site-settings", response_model=Dict[str, SettingValue])
12-
def site_settings(current_superuser: CurrentSuperuserDep):
13-
return SiteSetting.get_all_settings()
14+
def site_settings(user: CurrentSuperuserDep):
15+
return SiteSetting.get_all_settings(force_check_db_cache=True)
16+
17+
18+
class SettingUpdate(BaseModel):
19+
value: SettingType
20+
21+
22+
@router.put(
23+
"/admin/site-settings/{setting_name}",
24+
status_code=HTTPStatus.NO_CONTENT,
25+
responses={
26+
HTTPStatus.BAD_REQUEST: {
27+
"content": {
28+
"application/json": {
29+
"examples": {
30+
"invalid_data_type": {
31+
"summary": "Invalid data type",
32+
"value": {"detail": "title must be of type `str`"},
33+
},
34+
}
35+
}
36+
},
37+
},
38+
HTTPStatus.NOT_FOUND: {
39+
"content": {
40+
"application/json": {
41+
"examples": {
42+
"setting_not_found": {
43+
"summary": "Setting not found",
44+
"value": {"detail": "Setting not found"},
45+
},
46+
}
47+
}
48+
},
49+
},
50+
},
51+
)
52+
def update_site_setting(
53+
session: SessionDep,
54+
user: CurrentSuperuserDep,
55+
setting_name: str,
56+
request: SettingUpdate,
57+
):
58+
if not SiteSetting.setting_exists(setting_name):
59+
raise HTTPException(
60+
status_code=HTTPStatus.NOT_FOUND, detail="Setting not found"
61+
)
62+
63+
try:
64+
SiteSetting.update_setting(session, setting_name, request.value)
65+
except ValueError as e:
66+
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e))

backend/app/models/site_setting.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
class SiteSetting(UpdatableBaseModel, table=True):
99
id: Optional[int] = Field(default=None, primary_key=True)
10-
name: str = Field(max_length=256)
10+
name: str = Field(max_length=256, unique=True)
1111
data_type: str = Field(max_length=256)
1212
value: str = Field(sa_column=Column(JSON))
1313

backend/app/site_settings/__init__.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,31 @@ def get_db_last_updated_at(session: Session):
2929
return result.timestamp() if result else 0
3030

3131

32+
type_mapping = {
33+
"str": str,
34+
"int": int,
35+
"float": float,
36+
"bool": bool,
37+
"dict": dict,
38+
"list": list,
39+
}
40+
41+
3242
class SiteSettingProxy:
3343
__db_cache: dict = {}
3444
__last_updated_at_ts: float = 0
3545
__last_checked_at_ts: float = 0
3646
__mutex = threading.Lock()
3747

38-
def update_db_cache(self):
48+
def update_db_cache(self, force_check=False):
3949
with Session(engine) as session:
4050
# Check if we need to update the cache every 10 seconds,
4151
# so it means settings will not be updated in real-time
4252
# which is acceptable for this project.
4353
# If we need real-time updates in the future, we can use
4454
# a message queue or a pub/sub system to notify the app.
4555
now = time.time()
46-
if now - self.__last_checked_at_ts > 10:
56+
if force_check or (now - self.__last_checked_at_ts > 10):
4757
self.__last_checked_at_ts = now
4858
last_updated_at_ts = get_db_last_updated_at(session)
4959

@@ -89,8 +99,10 @@ def get_setting_group(self, group: str) -> dict[str, SettingValue]:
8999
)
90100
return result
91101

92-
def get_all_settings(self) -> dict[str, SettingValue]:
93-
self.update_db_cache()
102+
def get_all_settings(
103+
self, force_check_db_cache: bool = False
104+
) -> dict[str, SettingValue]:
105+
self.update_db_cache(force_check_db_cache)
94106

95107
result = {}
96108
for _, settings in default_settings.setting_groups.items():
@@ -106,8 +118,31 @@ def get_all_settings(self) -> dict[str, SettingValue]:
106118
)
107119
return result
108120

121+
def setting_exists(self, name: str) -> bool:
122+
return hasattr(default_settings, name)
123+
124+
def update_setting(self, session: Session, name: str, value: SettingType):
125+
if not self.setting_exists(name):
126+
raise AttributeError(f"Setting {name} does not exist.")
127+
128+
_default_setting: SettingValue = getattr(default_settings, name)
129+
if not isinstance(value, type_mapping[_default_setting.data_type]):
130+
raise ValueError(f"{name} must be of type `{_default_setting.data_type}`.")
131+
132+
db_setting_obj = session.exec(
133+
select(DBSiteSetting).filter(DBSiteSetting.name == name)
134+
).first()
135+
if db_setting_obj:
136+
db_setting_obj.value = value
137+
else:
138+
db_setting_obj = DBSiteSetting(
139+
name=name, value=value, data_type=_default_setting.data_type
140+
)
141+
session.add(db_setting_obj)
142+
session.commit()
143+
109144

110145
SiteSetting = SiteSettingProxy()
111146

112147

113-
__all__ = ["SiteSetting", "SettingValue"]
148+
__all__ = ["SiteSetting", "SettingValue", "SettingType"]

0 commit comments

Comments
 (0)