Skip to content

Commit

Permalink
feat(app):
Browse files Browse the repository at this point in the history
- notice debug duplication
- login signup api returns user info
- plugins data goes to .data
- add node check for manager
  • Loading branch information
MorvanZhou committed May 24, 2024
1 parent e2aefe4 commit 7c6adc3
Show file tree
Hide file tree
Showing 16 changed files with 147 additions and 35 deletions.
1 change: 0 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,3 @@
*.sw?

/temp
/src/retk/plugins/official_plugins/**/*.json
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,3 @@


/temp
/src/retk/plugins/official_plugins/**/*.json
4 changes: 1 addition & 3 deletions run_in_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@ def main():

os.environ["RETHINK_DEFAULT_LANGUAGE"] = os.environ.get("APP_LANGUAGE")
os.environ["RETHINK_LOCAL_STORAGE_PATH"] = os.getcwd()
os.environ["RETHINK_SERVER_HEADLESS"] = "0"
os.environ["RETHINK_SERVER_HEADLESS"] = "1"
os.environ["RETHINK_SERVER_DEBUG"] = "true"

pw = os.environ.get("APP_PASSWORD", "")
if pw != "":
if len(pw) < 6 or len(pw) > 20:
raise ValueError("Password length should be between 6 and 20 characters!")
os.environ["RETHINK_SERVER_PASSWORD"] = pw

_plugins_start_on_schedule()
Expand Down
36 changes: 26 additions & 10 deletions src/retk/controllers/account.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
from random import randint
from typing import Tuple
from typing import Tuple, Dict, Union

from fastapi.responses import StreamingResponse, JSONResponse

from retk import config, const, safety
from retk.controllers import schemas
from retk.controllers.utils import json_exception
from retk.core import account, user, statistic
from retk.models.tps import AuthedUser
from retk.models.tps import AuthedUser, UserMeta
from retk.utils import get_token, jwt_encode, jwt_decode


def set_cookie_response(uid: str, req_id: str, status_code: int, access_token: str, refresh_token: str):
def set_cookie_response(
u: Union[UserMeta, Dict[str, str]],
req_id: str,
status_code: int,
access_token: str,
refresh_token: str,
) -> JSONResponse:
if "id" not in u:
raise json_exception(
request_id=req_id,
code=const.CodeEnum.INVALID_AUTH,
language=const.LanguageEnum.EN.value,
log_msg="user id not found",
)
if len(u) > 1:
content = schemas.user.get_user_info_response_from_u_dict(u, request_id=req_id).model_dump()
else:
content = {"requestId": req_id}
resp = JSONResponse(
status_code=status_code,
content={
"requestId": req_id,
})
content=content,
)
s = config.get_settings()

resp.set_cookie(
Expand All @@ -41,7 +57,7 @@ def set_cookie_response(uid: str, req_id: str, status_code: int, access_token: s
)
resp.set_cookie(
key=const.settings.COOKIE_REFRESH_TOKEN_ID,
value=uid,
value=u["id"],
httponly=True, # prevent JavaScript from accessing the cookie, XSS
secure=safety.cookie_secure, # only send the cookie over HTTPS
samesite=safety.cookie_samesite, # prevent CSRF
Expand Down Expand Up @@ -82,7 +98,7 @@ async def signup(
language=req.language,
)
return set_cookie_response(
uid=new_user["id"],
u=new_user,
req_id=req_id,
status_code=201,
access_token=access_token,
Expand Down Expand Up @@ -131,7 +147,7 @@ async def login(
remark="",
)
return set_cookie_response(
uid=u["id"],
u=u,
req_id=req_id,
status_code=200,
access_token=access_token,
Expand Down Expand Up @@ -310,7 +326,7 @@ async def get_new_access_token(
},
)
return set_cookie_response(
uid=au.u.id,
u={"id": au.u.id},
req_id=au.request_id,
status_code=200,
access_token=access_token,
Expand Down
29 changes: 28 additions & 1 deletion src/retk/controllers/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,40 @@ async def get_user_info(
editorMode=u["settings"]["editorMode"],
editorFontSize=u["settings"]["editorFontSize"],
editorCodeTheme=u["settings"]["editorCodeTheme"],
editorSepRightWidth=u["settings"]["editorSepRightWidth"],
editorSepRightWidth=u["settings"].get("editorSepRightWidth", 200),
editorSideCurrentToolId=u["settings"]["editorSideCurrentToolId"],
),
),
)


async def get_node_info(
au: AuthedUser,
nid: str,
) -> schemas.manager.GetUserNodeResponse:
n, code = await account.manager.get_node_info(nid=nid)
maybe_raise_json_exception(au=au, code=code)
return schemas.manager.GetUserNodeResponse(
requestId=au.request_id,
node=schemas.manager.GetUserNodeResponse.Node(
id=n["id"],
uid=n["uid"],
md=n["md"],
title=n["title"],
snippet=n["snippet"],
type=n["type"],
disabled=n["disabled"],
inTrash=n["inTrash"],
modifiedAt=datetime2str(n["modifiedAt"]),
inTrashAt=datetime2str(n["inTrashAt"]) if n["inTrashAt"] is not None else None,
createdAt=datetime2str(n["_id"].generation_time),
fromNodeIds=n["fromNodeIds"],
toNodeIds=n["toNodeIds"],
history=n["history"],
),
)


async def disable_account(
au: AuthedUser,
req: schemas.manager.GetUserRequest,
Expand Down
4 changes: 2 additions & 2 deletions src/retk/controllers/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ async def provider_callback(provider_name: str, req: Request) -> JSONResponse:
)

return set_cookie_response(
uid=u["id"],
u=u,
req_id="",
status_code=200,
access_token=access_token,
Expand Down Expand Up @@ -129,7 +129,7 @@ async def provider_callback(provider_name: str, req: Request) -> JSONResponse:
language=language,
)
return set_cookie_response(
uid=u["id"],
u=u,
req_id="",
status_code=201,
access_token=access_token,
Expand Down
21 changes: 21 additions & 0 deletions src/retk/controllers/schemas/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,27 @@ class Settings(BaseModel):
user: User = Field(description="user info")


class GetUserNodeResponse(BaseModel):
class Node(BaseModel):
id: str
uid: str
md: str
title: str
snippet: str
type: int # const.NodeType.MARKDOWN.value
disabled: bool
inTrash: bool
modifiedAt: str
inTrashAt: Optional[str]
createdAt: str
fromNodeIds: List[str]
toNodeIds: List[str]
history: List[str]

requestId: str = Field(max_length=settings.REQUEST_ID_MAX_LENGTH, description="request ID")
node: Node = Field(description="node info")


class ManagerNoticeDeliveryRequest(BaseModel):
title: str = Field(
max_length=settings.MAX_SYSTEM_NOTICE_TITLE_LENGTH,
Expand Down
4 changes: 2 additions & 2 deletions src/retk/controllers/self_hosted.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from bson import ObjectId
from bson.tz_util import utc

from retk import const, __version__
from retk import const, __version__, utils
from retk.core import self_hosted, scheduler, user
from retk.core.notice import post_in_manager_delivery
from retk.logger import logger
Expand Down Expand Up @@ -102,7 +102,7 @@ async def notice_new_pkg_version():
__new_version_content_temp_zh if language == "zh" else __new_version_content_temp_en
).format(local_version_str, remote_version_str)
for notice in res:
if notice["title"] == __new_version_title_zh or notice["title"] == __new_version_title_en:
if notice["title"] == title and notice["html"] == utils.md2html(content):
return
await post_in_manager_delivery(
au=_local_system_authed_user,
Expand Down
7 changes: 7 additions & 0 deletions src/retk/core/account/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,10 @@ async def enable_by_uid(uid: str) -> const.CodeEnum:

async def enable_by_email(email: str) -> const.CodeEnum:
return await __disable_enable({"source": const.UserSourceEnum.EMAIL.value, "account": email}, disable=False)


async def get_node_info(nid: str) -> Tuple[Optional[tps.Node], const.CodeEnum]:
n = await client.coll.nodes.find_one({"id": nid})
if n is None:
return None, const.CodeEnum.NODE_NOT_EXIST
return n, const.CodeEnum.OK
20 changes: 10 additions & 10 deletions src/retk/dist-local/js/app.js

Large diffs are not rendered by default.

19 changes: 18 additions & 1 deletion src/retk/plugins/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def before_node_updated(self, uid: str, nid: str, data: Dict[str, Any]) -> None:
from dataclasses import dataclass
from typing import Dict, List, Optional, Any

from retk import const
from retk import const, config
from retk.core.scheduler.timing import Timing
from retk.models import tps

Expand Down Expand Up @@ -166,6 +166,23 @@ def api_call_url(self) -> str:
raise ValueError("the host or port number is not set in the environment.")
return f"{addr}/api/plugins/call"

@property
def data_dir(self) -> str:
"""
The data directory for the plugin.
Returns:
str: the data directory
"""
p = os.path.join(
config.get_settings().RETHINK_LOCAL_STORAGE_PATH,
const.settings.DOT_DATA,
"plugins",
self.id,
)
os.makedirs(p, exist_ok=True)
return p


event_plugin_map: Dict[str, List[Plugin]] = {
"on_node_added": [],
Expand Down
2 changes: 1 addition & 1 deletion src/retk/plugins/official_plugins/favorites/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class Favorites(retk.Plugin):

def __init__(self):
super().__init__()
self.data_path = Path(__file__).parent / ".data.json"
self.data_path = Path(self.data_dir) / ".data.json"
if not self.data_path.exists():
self.data = OrderedDict()
self.data_path.write_text(json.dumps(self.data))
Expand Down
2 changes: 1 addition & 1 deletion src/retk/plugins/official_plugins/summary/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class DailySummary(retk.Plugin):

def __init__(self):
super().__init__()
self.data_path = Path(__file__).parent / ".data.json"
self.data_path = Path(self.data_dir) / ".data.json"
today = datetime.now().strftime("%Y-%m-%d")
if not self.data_path.exists():
self.data = [{
Expand Down
5 changes: 5 additions & 0 deletions src/retk/routes/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
@router.post(
"/",
status_code=201,
response_model=schemas.user.UserInfoResponse,
)
@utils.measure_time_spend
async def signup(
Expand All @@ -30,6 +31,7 @@ async def signup(
@router.put(
"/auto-login",
status_code=200,
response_model=schemas.user.UserInfoResponse,
)
@utils.measure_time_spend
async def auto_login(
Expand All @@ -47,6 +49,7 @@ async def auto_login(
@router.put(
"/login",
status_code=200,
response_model=schemas.user.UserInfoResponse,
)
@utils.measure_time_spend
async def login(
Expand All @@ -60,6 +63,7 @@ async def login(
@router.put(
path="/password",
status_code=200,
response_model=schemas.RequestIdResponse,
)
@utils.measure_time_spend
async def forget_password(
Expand Down Expand Up @@ -87,6 +91,7 @@ async def email_verification(
@router.get(
"/access-token",
status_code=200,
response_model=schemas.RequestIdResponse,
)
@utils.measure_time_spend
async def refresh_token(
Expand Down
13 changes: 13 additions & 0 deletions src/retk/routes/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ async def get_user_info(
return await manager.get_user_info(au=au, req=req)


@router.get(
"/nodes/{nid}",
status_code=200,
response_model=schemas.manager.GetUserNodeResponse,
)
@utils.measure_time_spend
async def get_node_info(
au: ADMIN_AUTH,
nid: str,
) -> schemas.manager.GetUserNodeResponse:
return await manager.get_node_info(au=au, nid=nid)


@router.put(
"/users/disable",
status_code=200,
Expand Down
14 changes: 12 additions & 2 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ async def asyncSetUp(self) -> None:
self.assertEqual(842, len(self.access_token))
self.assertTrue(self.access_token.startswith("\"Bearer "), msg=self.access_token)
self.assertTrue(self.refresh_token.startswith("\"Bearer "), msg=self.access_token)
rj = resp.json()
self.assertEqual(200, resp.status_code, msg=rj)
self.assertNotIn("detail", rj)

self.default_headers = {
"RequestId": "xxx",
}
Expand Down Expand Up @@ -189,7 +193,10 @@ async def create_new_temp_user(self, email):
},
headers={"RequestId": "xxx"}
)
self.check_ok_response(resp, 201)
rj = self.check_ok_response(resp, 201)
self.assertEqual("xxx", rj["requestId"])
self.assertEqual(email, rj["user"]["email"])

return resp

async def set_default_manager(self):
Expand Down Expand Up @@ -218,6 +225,8 @@ async def test_auto_login(self):
rj = resp.json()
self.assertEqual("xxx", rj["requestId"])
self.assertIsNotNone(rj["user"])
self.assertEqual("rethink", rj["user"]["nickname"])
self.assertEqual("en", rj["user"]["settings"]["language"])

self.client.cookies.delete(const.settings.COOKIE_ACCESS_TOKEN)
resp = self.client.put(
Expand All @@ -228,12 +237,13 @@ async def test_auto_login(self):
self.assertIsNone(resp.json()["user"])

async def test_access_refresh_token(self):
self.client.put(
resp = self.client.put(
"/api/account/login",
json={
"email": const.DEFAULT_USER["email"],
"password": "",
})
self.assertEqual(200, resp.status_code)

resp = self.client.get(
"/api/users",
Expand Down

0 comments on commit 7c6adc3

Please sign in to comment.