Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
274 changes: 274 additions & 0 deletions :q

Large diffs are not rendered by default.

58 changes: 55 additions & 3 deletions app/api/v1/mail/ApiMailFolder.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
FolderExpungeResponseSchema,
FolderPurgeResponseSchema,
FolderShareResponseSchema,
FolderRenameSchema,
FolderRenameResponseSchema,
FolderTypeSchema,
FolderTypeResponseSchema,
)

if TYPE_CHECKING:
Expand Down Expand Up @@ -131,9 +135,6 @@ def patch(self, folder_data: dict, account_id: str, folder_name: str) -> Respons
:rtype: ResponseReturnValue
"""
raise NotImplementedError()
logger_api.debug("Calling ApiMailFolderId.patch for account_id: %s, folder_name: %s with data: %s", account_id, folder_name, folder_data)
interface: InterfaceApiMailFolder = g.inter
return interface.update_folder(account_id, folder_name, folder_data)

@blp.response(200, FolderDetailsResponseSchema, example=FolderDetailsResponseSchema.example())
def get(self, account_id: str, folder_name: str) -> ResponseReturnValue:
Expand Down Expand Up @@ -251,3 +252,54 @@ def post(self, share_data: list, account_id: str, folder_name: str) -> ResponseR
account_id, folder_name, share_data)
interface: InterfaceApiMailFolder = g.inter
return interface.share_folder(account_id, folder_name, share_data)


@blp.route("/<path:folder_name>/rename")
class ApiMailFolderIdRename(MethodView):
"""API to rename a specific mail folder.
"""
@blp.arguments(FolderRenameSchema, example=FolderRenameSchema.example())
@blp.response(200, FolderRenameResponseSchema, example=FolderRenameResponseSchema.example())
def post(self, rename_data: dict, account_id: str, folder_name: str) -> ResponseReturnValue:
"""Action: Rename the specified folder.

:param rename_data: The rename configuration (name)
:type rename_data: dict
:param account_id: The ID of the account
:type account_id: str
:param folder_name: The current path of the folder
:type folder_name: str
:return: ApiBaseResponse with renamed folder info
:rtype: ResponseReturnValue
"""
logger_api.debug("Calling ApiMailFolderIdRename.post for account_id: %s, folder_name: %s with data: %s",
account_id, folder_name, rename_data)
interface: InterfaceApiMailFolder = g.inter
return interface.rename_folder(account_id, folder_name, rename_data["name"])


@blp.route("/<path:folder_name>/type")
class ApiMailFolderIdType(MethodView):
"""API to change the type of a specific mail folder.
"""
@blp.arguments(FolderTypeSchema, example=FolderTypeSchema.example())
@blp.response(200, FolderTypeResponseSchema, example=FolderTypeResponseSchema.example())
def post(self, type_data: dict, account_id: str, folder_name: str) -> ResponseReturnValue:
"""Action: Change the type of the specified folder.

Only folders of type NORMAL can have their type changed.
Valid types are: SENT, DRAFT, JUNK, TRASH, TEMPLATE, PLANNED

:param type_data: The type configuration (type)
:type type_data: dict
:param account_id: The ID of the account
:type account_id: str
:param folder_name: The path of the folder
:type folder_name: str
:return: ApiBaseResponse with updated folder info
:rtype: ResponseReturnValue
"""
logger_api.debug("Calling ApiMailFolderIdType.post for account_id: %s, folder_name: %s with data: %s",
account_id, folder_name, type_data)
interface: InterfaceApiMailFolder = g.inter
return interface.change_folder_type(account_id, folder_name, type_data["type"])
96 changes: 96 additions & 0 deletions app/api/v1/mail/schemas/folder.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,3 +388,99 @@ def example(cls) -> dict:
}
}
}


class FolderRenameSchema(Schema):
"""
Schema for renaming a mail folder.
"""
name = fields.String(required=True)

@classmethod
def example(cls) -> dict:
"""
Example data for folder rename.

:return: Example folder rename payload.
:rtype: dict
"""
return {
"name": "NewFolderName"
}


class FolderRenameResponseSchema(ApiBaseResponse):
"""
Schema for POST /mailboxes/<account_id>/folders/<path:folder_name>/rename response
"""
data = fields.Dict(required=False, allow_none=True)

@classmethod
def example(cls) -> dict:
"""Example response for folder rename.

:return: Example folder rename response
:rtype: dict
"""
return {
"error_code": 0,
"error_msg": "",
"data": {
"name": "NewFolderName",
"path": "NewFolderName",
"subscribed": 1,
"type": "folder",
"unseen_count": 0,
"message_count": 10,
"children": []
}
}


class FolderTypeSchema(Schema):
"""
Schema for changing a mail folder type.
Valid types are: SENT, DRAFT, JUNK, TRASH, TEMPLATE, PLANNED
(note: only folders currently of type NORMAL can have their type changed)
"""
type = fields.String(required=True)

@classmethod
def example(cls) -> dict:
"""
Example data for folder type change.

:return: Example folder type change payload.
:rtype: dict
"""
return {
"type": "SENT"
}


class FolderTypeResponseSchema(ApiBaseResponse):
"""
Schema for POST /mailboxes/<account_id>/folders/<path:folder_name>/type response
"""
data = fields.Dict(required=False, allow_none=True)

@classmethod
def example(cls) -> dict:
"""Example response for folder type change.

:return: Example folder type change response
:rtype: dict
"""
return {
"error_code": 0,
"error_msg": "",
"data": {
"name": "MyFolder",
"path": "MyFolder",
"subscribed": 1,
"type": "SENT",
"unseen_count": 0,
"message_count": 10,
"children": []
}
}
1 change: 1 addition & 0 deletions app/config/settings/UserSettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ def get_all_user_settings_schema() -> list[Type[SogoSchema]]:
UserContactCategorySettings,
UserMailGeneralSettings,
UserMailCategorySettings,
UserMailViewSettings,
UserExtraSettings]
return all_schemas

Expand Down
40 changes: 40 additions & 0 deletions app/interface/mail/InterfaceApiMailFolder.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from app.auth.User import User
from app.module.mail.ModuleMail import ModuleMail
from app.module.user.ModuleUserProfile import ModuleUserProfile
from app.module.auth.ModuleUserSource import ModuleUserSource
from app.config.settings.DomainSettings import MailSettings, MailSettingsObj
from app.utils.exceptions import RequestException
Expand All @@ -29,6 +30,7 @@ def __init__(self, process_setting: ProcessSetting, user_domain_settings: dict,
self.user = user

self.mail_module = ModuleMail(self.user, self.mail_settings)
self.user_module = ModuleUserProfile(process_setting, user_domain_settings)

def get_folder_list(self, account_id: str) -> tuple[dict[str, Any], int]:
"""Retrieve the list of mail folders for a given account and return an ApiBaseResponse.
Expand Down Expand Up @@ -128,6 +130,44 @@ def update_folder(self, account_id: str, folder_name: str, folder_data: dict[str
logger_api.error("Request exception in update_folder: %s", str(ex))
return create_api_base_response(None, ex.error)

def rename_folder(self, account_id: str, folder_path: str, new_name: str) -> tuple[dict[str, Any], int]:
"""Rename a mail folder.

:param account_id: The ID of the account
:type account_id: str
:param folder_path: The current path of the folder
:type folder_path: str
:param new_name: The new name for the folder
:type new_name: str
:return: A tuple of (API response dict, status code)
:rtype: tuple[dict[str, Any], int]
"""
try:
renamed_folder = self.mail_module.rename_folder(account_id, folder_path, new_name)
return create_api_base_response(renamed_folder)
except RequestException as ex:
logger_api.error("Request exception in rename_folder: %s", str(ex))
return create_api_base_response(None, ex.error)

def change_folder_type(self, account_id: str, folder_path: str, new_type: str) -> tuple[dict[str, Any], int]:
"""Change the type of a mail folder.

:param account_id: The ID of the account
:type account_id: str
:param folder_path: The path of the folder
:type folder_path: str
:param new_type: The new type for the folder
:type new_type: str
:return: A tuple of (API response dict, status code)
:rtype: tuple[dict[str, Any], int]
"""
try:
updated_folder = self.user_module.change_folder_type(self.user, self.mail_settings, folder_path, new_type)
return create_api_base_response(updated_folder)
except RequestException as ex:
logger_api.error("Request exception in change_folder_type: %s", str(ex))
return create_api_base_response(None, ex.error)

def expunge_folder(self, account_id: str, folder_name: str, expunge_data:dict) -> tuple[dict[str, Any], int]:
"""Expunge all mails in the specified folder.

Expand Down
12 changes: 12 additions & 0 deletions app/manager/mail/ClientMailServer.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@ def delete_folder(self, folder_path: str, do_children:bool = True) -> None:
do_children = True means all the children/subfolders will be affected too.
"""

@abstractmethod
def rename_folder(self, old_name: str, new_name: str) -> None:
"""
Rename a folder (mailbox) on the mail server.

:param old_name: The current name of the folder.
:type old_name: str
:param new_name: The new name for the folder.
:type new_name: str
:raises RequestException: If not connected to the server or if renaming fails.
"""

@abstractmethod
def purge_folder(self, folder_path: str, before_date: str = "", do_children: bool = True, permanently: bool = False) -> int:
"""
Expand Down
Loading
Loading