Skip to content

Commit

Permalink
0.6.12
Browse files Browse the repository at this point in the history
  • Loading branch information
TheCommCraft committed Jun 30, 2024
1 parent 10af969 commit f3c6eca
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 52 deletions.
49 changes: 28 additions & 21 deletions dungeonmaker/dm_backend/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def load_private_profile() -> json.dumps:
def load_profile(username : str = None, *, user_id : str = None) -> json.dumps:
user : User
try:
user = self.dm_session.find(USER, id=user_id, name=username)
user = self.dm_session.find(USER, __id=user_id, name=username)
except KeyError:
raise ErrorMessage(json.dumps({"success": False, "result": None, "reason": "That profile doesn't seem to exist."}))
user_data = s_vars(user)
Expand Down Expand Up @@ -181,13 +181,13 @@ def reset_password(username : str, password : str = None, linked_user : str = No
if code is None:
code = secrets.randbits(24)
self.current_client_data["password_reset_code"] = code
return f"Your password reset code is \"{code}\". Try commenting \"Password reset code: {code}\" using your linked account."
return f"Your password reset code is \"{code}\". Comment \"Password reset code: {code}\" using your linked account."
if code != self.current_client_data["password_reset_code"]:
raise ErrorMessage("Wrong code.")
user = self.dm_session.find(USER, name=username)
if user.linked_user is None:
raise ErrorMessage("You do not have a user linked, so you cannot reset your password like this. Try commenting on the project for help.")
if linked_user != user.linked_user:
if user.linked_user is None:
raise ErrorMessage("You do not have a user linked, so you cannot reset your password like this. Try commenting on the project for help.")
raise ErrorMessage("That is not your linked user.")
project = self.project
comment = f"Password reset code: {code}"
Expand All @@ -207,7 +207,7 @@ def logout() -> str:
return "OK"

@self.request_handler.request(name="save_dungeon", allow_python_syntax=True, auto_convert=True)
def save_dungeon(start_room : RoomId, start_x : int, start_y : int, name : str, dungeon_id=None):
def save_dungeon(start_room : RoomId, start_x : int, start_y : int, name : str = None, dungeon_id : DungeonId = None):
dungeon : Dungeon
self.ensure_login()
try:
Expand All @@ -221,10 +221,21 @@ def save_dungeon(start_room : RoomId, start_x : int, start_y : int, name : str,
"owner_name": self.current_client_data["username"],
"start": ()
})
if not name:
raise ErrorMessage("You need to pick a name.")
if not find_comment(self.project, content=f"Set name of {dungeon.dungeon_id} to {name}"):
raise ErrorMessage(f"Could not confirm name. Comment \"Set name of {dungeon.dungeon_id} to {name}\" and try again.")
user = self.find_current_client_user()
user.remaining_dungeons -= 1
user.owned_dungeons.append(dungeon_id)
user.permitted_dungeons.append(dungeon_id)
user.write()
if not dungeon.get_user(self.current_client_data["username"]).permitions.get("edit_infos"):
raise ErrorMessage("Not Authorized")
#if not find_comment(self.pr)
dungeon.name = name
if name:
if not find_comment(self.project, content=f"Set name of {dungeon.dungeon_id} to {name}"):
raise ErrorMessage(f"Could not confirm name. Comment \"Set name of {dungeon.dungeon_id} to {name}\" and try again.")
dungeon.name = name
dungeon.start = (start_room, start_x, start_y)
dungeon.write()

Expand All @@ -240,24 +251,24 @@ def save_room(room_id : RoomId, content : str, bound_dungeon : DungeonId) -> str
try:
room = self.dm_session.find(ROOM, room_id)
except KeyError:
dungeon = self.dm_session.find(DUNGEON, bound_dungeon)
if not (
(permitions := dungeon.get_user(user_id=self.current_client_data["user_id"]).permitions).get("edit_room") == room_id or
permitions.get("edit_rooms")
):
try:
dungeon = self.dm_session.find(DUNGEON, bound_dungeon)
except KeyError:
raise ErrorMessage("Dungeon does not exist.")
if not dungeon.permissions[self.current_client_data["user_id"]].can_edit_room(room_id=room_id):
raise ErrorMessage("Not authorized")
room = dungeon.new_room(room_id=room_id)
user = self.find_current_client_user()
user.remaining_rooms -= 1
user.write()
else:
if not room.dungeon_id == bound_dungeon:
raise ErrorMessage("Wrong dungeon bound.")
try:
dungeon = self.dm_session.find(DUNGEON, bound_dungeon)
except KeyError:
raise ErrorMessage("Dungeon does not exist.")
if not (
(permitions := dungeon.get_user(user_id=self.current_client_data["user_id"]).permitions).get("edit_room") == room_id or
permitions.get("edit_rooms")
):
if not dungeon.permissions[self.current_client_data["user_id"]].can_edit_room(room_id=room_id):
raise ErrorMessage("Not authorized")
room.content = content
room.write()
Expand Down Expand Up @@ -293,6 +304,7 @@ def unlike_dungeon(dungeon_id : DungeonId) -> str:

@self.request_handler.request(name="load_tab", allow_python_syntax=True, auto_convert=True)
def load_tab(tab : str) -> json.dumps:
data = []
if tab == "popular":
data = self.dm_session.get_popular_tab()
elif tab == "random":
Expand Down Expand Up @@ -340,8 +352,6 @@ def find_current_client_user(self) -> User:





def gen_passdata(*, username : str, password : str) -> bytes:
"""
Generate passdata for an account
Expand Down Expand Up @@ -395,6 +405,3 @@ def include_data(data : dict, *, include : list[str] = ()) -> dict:






14 changes: 12 additions & 2 deletions dungeonmaker/dm_backend/modules/dm/dmtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
from __future__ import annotations
from typing import Literal, Any, Union, Sequence, Mapping
from weakref import WeakValueDictionary
from dataclasses import dataclass, field
import secrets, time

Expand Down Expand Up @@ -121,8 +122,15 @@ def get(self, __type : Any) -> Permission:
return ([i for i in self if i.type == __type] + [Permission(type=__type, value=None)])[0]
except IndexError:
pass



def get_all(self, __type : Any) -> list[Permission]:
return [i for i in self if i.type == __type]

def can_edit_room(self, *, room_id : RoomId = None, room : BaseRoom = None):
room_id = room_id or room.room_id
if self.get("edit_rooms"):
return True
return any(i.value == room_id for i in self.get_all("edit_room"))

class Stats(dict):
"""
Expand Down Expand Up @@ -177,6 +185,8 @@ class BaseUser:
session : BaseDMSession = field(kw_only=True)
passdata : bytes = field(kw_only=True)
linked_user : Union[str, None] = field(kw_only=True, default=None)
remaining_dungeons : int = field(kw_only=True, default=16)
remaining_rooms : int = field(kw_only=True, default=128)
stats : Stats = field(kw_only=True, default_factory=Stats)


Expand Down
20 changes: 17 additions & 3 deletions dungeonmaker/dm_backend/modules/dm/room.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
"""
from __future__ import annotations
from typing import Self
from .dmtypes import RoomId, BaseDungeon, BaseRoom
from . import dungeon
from .dmtypes import RoomId, BaseDungeon, BaseRoom, UserId
from . import dungeon, user
from . import session as _session
from .utils import s_vars
from .selectors import DUNGEON

class Room(BaseRoom):
"""
Expand Down Expand Up @@ -35,7 +36,20 @@ def write(self):
self.session.database_abstraction.insert_room(data=s_vars(self))
return
self.session.database_abstraction.update_room(room_id=self.room_id, updator={"$set": s_vars(self)})


def get_dungeon(self):
"""
Get the corresponding dungeon.
"""
return self.session.find(DUNGEON, self.dungeon_id)

def can_be_edited(self, *, user : user.User = None, user_id : UserId) -> bool:
"""
Return whether a certain user can edit a room.
"""
user_id = user_id or user.user_id
__dungeon = self.get_dungeon()
return __dungeon.permissions[user_id].can_edit_room(self.room_id)



Expand Down
95 changes: 73 additions & 22 deletions dungeonmaker/dm_backend/modules/dm/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""
from __future__ import annotations
import random
from weakref import WeakValueDictionary
from typing import Literal, Union, assert_never, Sequence, Mapping
from dataclasses import dataclass, field
from . import dungeon, user, room
Expand All @@ -12,10 +13,21 @@

@dataclass
class DMSession:
database_abstraction : _dba.DatabaseAbstractionSelector
database_abstractions : list[BaseDatabaseAbstraction]
_cached : dict[WeakValueDictionary[str, Union[dungeon.Dungeon, user.User, room.Room]]]

def __init__(self, *, database_abstractions : list = None):
self.database_abstractions = list(database_abstractions or ())
self.database_abstraction = _dba.DatabaseAbstractionSelector(self.database_abstractions)
self.setup_cache()

def setup_cache(self):
self._cached = {}
self._cached["dungeons"] = WeakValueDictionary()
self._cached["users"] = WeakValueDictionary()
self._cached["rooms"] = WeakValueDictionary()


def add_database_abstraction(self, dba : BaseDatabaseAbstraction):
"""
Expand Down Expand Up @@ -74,11 +86,11 @@ def search_for_term(self, term : str, *, amount : int = 10) -> list[dungeon.Dung
"$addFields": {
"score": {
"$add": [
{"$multiply":
{"$multiply":
[
20, {"$size": "$likers" }
]
},
]
},
"$views"
]
}
Expand All @@ -87,31 +99,70 @@ def search_for_term(self, term : str, *, amount : int = 10) -> list[dungeon.Dung
]
return [dungeon.Dungeon(**dungeon_data) for dungeon_data in self.database_abstraction.sorted_dungeons(amount=amount, field="score", aggregation=aggregator)]

def find(self, type : Literal["dungeon", "room", "user"], id : Union[DungeonId, RoomId, UserId] = None, *, name : str = None) -> Union[dungeon.Dungeon, room.Room, user.User]:
def lookup_cache(self, cache_type : str, __id : Union[DungeonId, RoomId, UserId]) -> Union[None, dungeon.Dungeon, room.Room, user.User]:
"""
Finds something.
Don't use.
"""
if __id in (cache := self._cached[cache_type]):
return cache[__id]

def save_cache(self, cache_type : str, __id : Union[DungeonId, RoomId, UserId], value : Union[dungeon.Dungeon, room.Room, user.User]):
"""
Don't use.
"""
if type == DUNGEON:
return dungeon.Dungeon.read(dungeon_id=id, session=self)
if type == ROOM:
return room.Room.read(id, session=self)
if type == USER:
return user.User.lookup_user(user_id=id, username=name, session=self)
assert_never(type)
self._cached[cache_type][__id] = value

def create(self, type : Literal["dungeon", "room", "user"], *, args : Sequence = (), kwargs : Mapping = {}) -> Union[dungeon.Dungeon, room.Room, user.User]:
def find(self, __type : Literal["dungeon", "room", "user"], __id : Union[DungeonId, RoomId, UserId] = None, *, name : str = None) -> Union[dungeon.Dungeon, room.Room, user.User]:
"""
Finds something.
"""
if __type == DUNGEON:
if (value := self.lookup_cache("dungeons", __id)):
return value
__dungeon = dungeon.Dungeon.read(dungeon_id=__id, session=self)
self.save_cache("dungeons", __id, __dungeon)
return __dungeon
if __type == ROOM:
if (value := self.lookup_cache("rooms", __id)):
return value
__room = room.Room.read(__id, session=self)
self.save_cache("rooms", __id, __room)
return __room
if __type == USER:
if (value := self.lookup_cache("users", __id)):
return value
__user = user.User.lookup_user(user_id=__id, username=name, session=self)
self.save_cache("users", __id, __user)
return __user
assert_never(__type)

def create(self, __type : Literal["dungeon", "room", "user"], *, args : Sequence = (), kwargs : Mapping = None) -> Union[dungeon.Dungeon, room.Room, user.User]:
"""
Creates something.
"""
args = list(args)
kwargs = dict(kwargs)
if type == DUNGEON:
return dungeon.Dungeon(*args, session=self, **kwargs)
if type == ROOM:
return room.Room(*args, session=self, **kwargs)
if type == USER:
return user.User(*args, session=self, **kwargs)
assert_never(type)
kwargs = kwargs or {}
if __type == DUNGEON:
__dungeon = dungeon.Dungeon(*args, session=self, **kwargs)
__id = __dungeon.dungeon_id
if self.lookup_cache("dungeons", __id):
raise ValueError("Dungeon already exists (in cache!!)")
self.save_cache("dungeons", __id, __dungeon)
return __dungeon
if __type == ROOM:
__room = room.Room(*args, session=self, **kwargs)
__id = __room.room_id
if self.lookup_cache("rooms", __id):
raise ValueError("Room already exists (in cache!!)")
self.save_cache("rooms", __id, __room)
return __room
if __type == USER:
__user = user.User(*args, session=self, **kwargs)
__id = __user.user_id
if self.lookup_cache("users", __id):
raise ValueError("User already exists (in cache!!)")
self.save_cache("users", __id, __user)
return __user
assert_never(__type)



Expand Down
2 changes: 1 addition & 1 deletion dungeonmaker/dm_backend/modules/dm/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ def s_vars(__obj) -> dict:
"""
Use like vars() but for objects with __slots__.
"""
return {slot: getattr(__obj, slot) for slot in __obj.__slots__ if not slot in ["_id", "session"]}
return {slot: getattr(__obj, slot) for slot in __obj.__slots__ if not slot in ["_id", "session", "_cached"]}
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name='dungeonmaker',
version='0.6.11',
version='0.6.12',
author='Simon Gilde',
author_email='[email protected]',
description='Dungeon maker backend and other things',
Expand All @@ -17,13 +17,13 @@
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
],
keywords=['scratch'],
install_requires=[
'scratchcommunication',
'pymongo',
'scratchattach',
],
python_requires='>=3.10',
python_requires='>=3.11',
)

0 comments on commit f3c6eca

Please sign in to comment.