diff --git a/.gitignore b/.gitignore index bad9e23d..9a8212b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ node_modules -/Backend/ \ No newline at end of file +*.pyc +Backend/static/images/posts/* +Backend/static/images/users/* +.env \ No newline at end of file diff --git a/Backend/app.py b/Backend/app.py index 647c488d..6254be5f 100644 --- a/Backend/app.py +++ b/Backend/app.py @@ -83,7 +83,7 @@ async def on_startup() -> None: [UserController, CommunityController, PostdayController, InitializeController, TagController, login_handler, logout_handler], # List of endpoint functions dependencies={"session": provide_transaction}, # Dependency to inject session into endpoints plugins=[SQLAlchemyPlugin(db_config)], # Plugin for SQLAlchemy support - stores=StoreRegistry(default_factory=cache.redis_store_factory), + stores=StoreRegistry(default_factory=cache.redis_store_factory), # Redis setup openapi_config=openapi.config, # OpenAPI configuration for Swagger UI on_startup=[on_startup], # Startup event handler to initialize DB tables on_app_init=[oauth2_auth.on_app_init], # Startup event handler to initialize OAuth2 @@ -91,6 +91,6 @@ async def on_startup() -> None: static_files_config=[ # Static files configuration for user and post images StaticFilesConfig(directories=['static/images/users'], path='/user/image'), StaticFilesConfig(directories=['static/images/posts'], path='/post/image'), - + StaticFilesConfig(directories=['static/images/communities'], path='/community/image'), # Images provided by Freepik ] ) \ No newline at end of file diff --git a/Backend/controllers/user.py b/Backend/controllers/user.py index 83b77465..16a16931 100644 --- a/Backend/controllers/user.py +++ b/Backend/controllers/user.py @@ -191,11 +191,9 @@ async def create_community(self, request: 'Request[User, Token, Any]', session: ''' current_time = datetime.datetime.now(pytz.utc) - commiunity_data = data.create_instance(id=uuid7(), users=[ - ], created_at=current_time, updated_at=current_time, postdays=[], tags=[]) + commiunity_data = data.create_instance(id=uuid7(), users=[], created_at=current_time, updated_at=current_time, postdays=[], tags=[]) + validated_community_data = CommunitySchema.model_validate(commiunity_data) - validated_community_data = CommunitySchema.model_validate( - commiunity_data) try: community = Community(**validated_community_data.__dict__) session.add(community) @@ -212,17 +210,16 @@ async def create_community(self, request: 'Request[User, Token, Any]', session: tag = await get_tag_by_name(session, tags[i].__dict__['name']) community.tags.append(tag) - validated_community_data = CommunitySchema.model_validate(commiunity_data) - await user_join_community(session, validated_community_data.id, request.user, 'owner') + await session.commit() - return validated_community_data except Exception as e: raise HTTPException( status_code=409, detail=f'Error creating community: {e}') + @post('/{communityID:str}/leave') async def leave_community(self, request: 'Request[User, Token, Any]', session: AsyncSession, communityID: str) -> str: ''' @@ -238,6 +235,7 @@ async def leave_community(self, request: 'Request[User, Token, Any]', session: A ''' return await user_leave_community(session, communityID, request.user) + @patch('/profile_image', media_type=MediaType.TEXT) async def update_profile_picture(self, request: 'Request[User, Token, Any]', session: AsyncSession, data: Annotated[UploadFile, Body(media_type=RequestEncodingType.MULTI_PART)]) -> str: user = await get_user_by_id(session, request.user) @@ -313,7 +311,7 @@ async def create_post(self, request: 'Request[User, Token, Any]', session: Async file_path = os.path.join(image_dir, filename) async with aiofiles.open(file_path, 'wb') as outfile: await outfile.write(image) - return f"File(s) created" + return f"File created" @@ -321,7 +319,7 @@ async def create_post(self, request: 'Request[User, Token, Any]', session: Async @post('/TransferOwnership/{id:str}/{newUser:str}') async def transfer_ownership(self, request: 'Request[User, Token, Any]', session: AsyncSession, communityID: str, newUserID: str) -> str: user = await get_user_by_id(session, request.user) - await transfer_community_ownership(session, id, user, newUserID) + await transfer_community_ownership(session, communityID, user, newUserID) return f"Ownership Transfered to {newUserID}" diff --git a/Backend/crud/post.py b/Backend/crud/post.py index d5428131..058b61b3 100644 --- a/Backend/crud/post.py +++ b/Backend/crud/post.py @@ -6,6 +6,17 @@ from models.post import Post async def get_posts_list(session: AsyncSession, limit: int, offset: int) -> list[Post]: + """ + Fetches a paginated list of Posts. + + Args: + session: Database session for query execution. + limit: Maximum number of Posts to return. + offset: Number of Posts to skip before returning the results. + + Returns: + A list of Post instances within specified limit and offset. + """ query = select(Post).limit(limit).offset(offset) result = await session.execute(query) @@ -13,6 +24,19 @@ async def get_posts_list(session: AsyncSession, limit: int, offset: int) -> list async def get_posts_by_id(session: AsyncSession, post_id: UUID) -> Post: + """ + Fetches a single Post by its ID. + + Args: + session: Database session for query execution. + post_id: UUID of the Post to fetch. + + Returns: + The Post object if found, None otherwise. + + Raises: + HTTPException: If there's an issue retrieving the Post. + """ query = select(Post).where(Post.id == post_id) result = await session.execute(query) try: @@ -21,7 +45,20 @@ async def get_posts_by_id(session: AsyncSession, post_id: UUID) -> Post: raise HTTPException(status_code=401, detail="Error retrieving post") -async def get_posts_by_user_id(session: AsyncSession, user_id: UUID) -> list[Post]: # WIP +async def get_posts_by_user_id(session: AsyncSession, user_id: UUID) -> list[Post]: + """ + Fetches all Posts made by a specific user. + + Args: + session: Database session for query execution. + user_id: UUID of the user whose Posts are to be retrieved. + + Returns: + A list of Post instances made by the specified user. + + Raises: + HTTPException: If there's an issue retrieving the Posts. + """ query = select(Post).where(Post.user_id == user_id) result = await session.execute(query) try: @@ -30,7 +67,22 @@ async def get_posts_by_user_id(session: AsyncSession, user_id: UUID) -> list[Pos raise HTTPException(status_code=401, detail="Error retrieving user posts") -async def get_posts_by_community_id(session: AsyncSession, community_id: UUID, limit: int=100, offset: int=0) -> list[Post]: # WIP +async def get_posts_by_community_id(session: AsyncSession, community_id: UUID, limit: int=100, offset: int=0) -> list[Post]: + """ + Fetches a paginated list of Posts belonging to a specific community. + + Args: + session: Database session for query execution. + community_id: UUID of the community whose Posts are to be retrieved. + limit: Maximum number of Posts to return (default 100). + offset: Number of Posts to skip before returning the results (default 0). + + Returns: + A paginated list of Post instances belonging to the specified community. + + Raises: + HTTPException: If there's an issue retrieving the Posts. + """ query = select(Post).where(Post.community_id == community_id).limit(limit).offset(offset) result = await session.execute(query) try: diff --git a/Backend/crud/tag.py b/Backend/crud/tag.py index fc9661e8..541bf060 100644 --- a/Backend/crud/tag.py +++ b/Backend/crud/tag.py @@ -7,6 +7,19 @@ async def get_tag_by_name(session: AsyncSession, name: str) -> Tag: + """ + Fetches a Tag by its name. + + Args: + session: Database session for query execution. + name: Name of the Tag to fetch. + + Returns: + The Tag object if found, None otherwise. + + Raises: + HTTPException: If no Tag is found or multiple Tags are found. + """ query = select(Tag).where(Tag.name == name) result = await session.execute(query) try: @@ -16,6 +29,16 @@ async def get_tag_by_name(session: AsyncSession, name: str) -> Tag: async def create_postday(session: AsyncSession, name: str) -> Tag: + """ + Creates a new Tag with a given name. + + Args: + session: Database session for committing new Tag. + name: Name for the new Tag. + + Returns: + The newly created Tag object. + """ tag = Tag(id=uuid7(), name=name) session.add(tag) await session.commit() diff --git a/Backend/crud/users.py b/Backend/crud/users.py index 72ee7b5b..232eba68 100644 --- a/Backend/crud/users.py +++ b/Backend/crud/users.py @@ -7,7 +7,6 @@ from models.user_community_association import UserCommunityAssociation from schemas.users import UserSchema from models.community import Community -from schemas.community import CommunitySchema async def user_join_community(session: AsyncSession, communityID: UUID, user_id: User, role: str = "member") -> UserSchema: """ @@ -120,7 +119,7 @@ async def get_user_by_id(session: AsyncSession, id: UUID) -> User: raise HTTPException(status_code=401, detail="Error retrieving user") -async def transfer_community_ownership(session: AsyncSession, id: UUID, user: User, new_owner: str) -> Community: +async def transfer_community_ownership(session: AsyncSession, id: UUID, user: User, new_owner: str) -> str: """ Transfer ownership of a community to a new user. @@ -135,13 +134,12 @@ async def transfer_community_ownership(session: AsyncSession, id: UUID, user: Us Raises: HTTPException: If there's an error retrieving the community or if the community doesn't exist. """ - community = await get_community_by_id(session, id) + if community is None: raise HTTPException(status_code=401, detail="Community not found") - - new_owner = await get_user_by_id(session, new_owner) + new_owner = await get_user_by_id(session, new_owner) query = select(UserCommunityAssociation).where(UserCommunityAssociation.community_id == id, UserCommunityAssociation.user_id == user.id) result = await session.execute(query) user_association = result.scalar_one_or_none() diff --git a/Backend/models/community.py b/Backend/models/community.py index 878c862c..0c962b40 100644 --- a/Backend/models/community.py +++ b/Backend/models/community.py @@ -27,3 +27,5 @@ class Community(UUIDAuditBase): secondary=community_tag_association, # lazy='selectin' ) + + image: Mapped[str] = mapped_column(String(100), nullable=True) diff --git a/Backend/schemas/community.py b/Backend/schemas/community.py index 0ab92ea3..5286664f 100644 --- a/Backend/schemas/community.py +++ b/Backend/schemas/community.py @@ -1,4 +1,5 @@ from __future__ import annotations +from typing import Optional from uuid import UUID @@ -6,12 +7,17 @@ from litestar.contrib.pydantic import PydanticDTO from .schema import Schema - +from datetime import datetime + class CommunitySchema(Schema): id: UUID # owner_id: UUID name: str description: str + image: Optional[str] = None + created_at: datetime + updated_at: datetime + users: list[UserCommunityAssociationSchema] = None postdays: list[PostdaySchema] @@ -19,6 +25,8 @@ class CommunitySchema(Schema): tags: list[TagSchema] + + class BaseCommunitySchema(Schema): id: UUID owner_id: UUID diff --git a/Backend/static/images/communities/painting1.jpg b/Backend/static/images/communities/painting1.jpg new file mode 100644 index 00000000..2f13d2eb Binary files /dev/null and b/Backend/static/images/communities/painting1.jpg differ