- BoardBloom
- Product Vision
- System Requirements
- Demo
- Stories
- App Design
- UML Diagram
- Architecture Overview
- Data Model
- Controllers
With BoardBloom, users can create, share, and find "Blooms," or visual material on the web. Users get the option to save Blooms they like on their boards. Boards work as topical collections that let users group their Blooms corresponding to projects or interests. A user may create distinct boards, for instance, for dinner ideas, cars or haircuts. The main forms of engagement on the platform are creating boards and Blooms, while liking and commenting are supported as well.
BoardBloom is designed to be a platform for discovering, creating, and sharing visual content online. Our goal is to make it easy and fun for users to organize their favorite images and ideas into boards that match their interests or projects. By offering a simple, engaging way to explore, save, and connect with content and other people, we strive to build a creative and inspiring space where everyone can share what they love and discover new ideas.
Functionalities | Description | Current State |
---|---|---|
User Authentication | Secure login/signup/logout for users. | Done |
Admin Role | Admin ability to add/remove/ban products, users and comments. | Done |
Home Page | The first page the user sees that contains the most popular posts | Done |
Posts(Blooms) | Repesents the user's post, complete with title, description, and picture. | Done |
Posts(Boards) | Repesents the user's collection of saved blooms. | Done |
Profile Page | A specific page where the user can show his collections. | Done |
Efficient Editing | Ability to edit a post afte posting it | Done |
User-Post interaction | Ability of the user to interact with posts through likes and comments | Done |
Efficient Search Function | Precise search for finding posts realted to specific topics. | Not Done |
Profile Page customization | Ability to cusomize a users profile page. | Not Done |
Comunities | Group chats the are created around a topic where peope can share pictures with members of that comunity only | Not Done |
Link demo YouTube: here.
- .NET 6.0 SDK
- Visual Studio 2022 or later
- SQL Server or any compatible database system
- A web browser (latest versions of Chrome, Firefox, or Edge)
-
As an administrator, I want to create, view, edit, and delete categories directly from the application interface, enabling flexible content organization.
-
As a visitor (registered or not), I want to view all bookmarks added to the platform, allowing me to explore content without restrictions.
-
As a user, I want to see the most recent and popular posts on the main page for better navigation.
-
As a registered user, I need to add posts with title, description, and embedded media (text, images, and videos) to the platform, expanding the content available to the community.
-
As a registered user, I want to comment on for posts, edit and delete my comments, fostering an interactive and engaged community.
-
As a registered user I want to have the ability to like a post, and also to remove my like
-
As a registered user, I need to organize posts into bookmarks on my personal page, allowing me to personalize my experience and find content easily.
-
As an administrator, I am responsible for content moderation, including the ability to delete inappropriate bookmarks or comments, ensuring the platform remains a safe and welcoming space.
-
As an administrator, I want to ban users from the application interface, mainting organizing groups.
-
As a developer, I need to design a database schema that supports communities, users, roles, posts, bookmarks, comments, categories, and search functionality, ensuring scalability and performance.
-
As a registered user, I want to create a community with a name, description, and optional image, so I can build a shared space for specific interests.
-
As a registered user, I want to join and leave communities, allowing me to choose the groups I want to participate in and engage with.
-
As a community member, I want to share posts (text, images) within the community, allowing me to interact and share content with members who have similar interests.
-
As a community member, I want to comment on and like posts shared within the community, fostering interaction and engagement within the group.
-
As a user, I want to discover and search for communities by name or category, helping me find groups that match my interests.
-
As an administrator, I need to view, manage, or delete any community if necessary, maintaining overall platform integrity and preventing abuse.
The development of the app was monitored using Trello.The following screenshot shows the Trello board used to track the progress of the project during development.
- Home Page Design
- Multiple cards (with photo, user, description, like count and date of posting).
- View Bloom Design
- Card (with photo, user, description, like count and date of posting)
- Comment list
- Form for adding comments
- One button for accessing user profile(the username)
-
Other options for blooms
-
Create Bloom Design
- Two buttons(for choosing the type of post)
- Two text fields(one for description and one for image url/text(for text post))
- One button for preview
- Edit Bloom Design
- One field for description
- One field for image url/text(for text post)
- Preview Bloom
- One card showing the bloom
- Button for returning to editing
- Button for applying the changes/posting
- Create Board Design
- Two fields(one for Name and one for Description)
- One button to create the board
- Boards Design
- Cards showing the preview of boards(clickable-redirects to view board)
- Button for adding a new board
- View Board Design
- Image preview of the saved blooms (clickable-redirects to view bloom)
- Two buttons for editing and deleting(shown by hovering on the 3 dots)
- Remove button for deleting a bloom from the board (shown by hovering on the bloom image)
- Edit Board Design
- Two fields(one for Name and one for Description)
- One button to save the board
- Profile Page Design
- Section for user specifics(profile image, email, number of blooms and boards)
- Cards showing user's boards
- Cards showing user's blooms
- Register Form Page Design
- Three fields (one for email, one for password and one for confirming the password)
- One button for confirming registration
- Login Form Page Design
- Two field (one for email and one for password)
- A checkbox for remembering the account
- A button for logging in
classDiagram
class User {
+ username: string
+ email: string
+ password: string
+ role: string
}
class Admin {
+ username: string
+ email: string
+ password: string
+ role: string
}
class Bloom {
+Int bloomId
+String title
+String description
+String imageUrl
+Date createdAt
+User createdBy
+addComment()
+addLike()
}
class Board {
+Int boardId
+String name
+String description
+Date createdAt
+User createdBy
+addBloom()
+removeBloom()
}
class Like {
+Int likeId
+User user
+Bloom bloom
}
class Comment {
+Int commentId
+String text
+User user
+Bloom bloom
+Date createdAt
}
User <|-- Admin
User --> Bloom
User --> Board
User --> Like
User --> Comment
Board --> Bloom
Bloom --> Like
Bloom --> Comment
BoardBloom is built using the MVC pattern to separate concerns:
- Model: Represents the data and business logic.
- View: Represents the presentation layer.
- Controller: Handles user input and interactions.
Represents the users of the application.
- UserId (int): Unique identifier for the user.
- Username (string): User's username.
- Email (string): User's email address.
- PasswordHash (string): Hash of the user's password.
- Role: Role of the user (Admin or User).
Represents individual posts created by users.
- BloomId (int): Unique identifier for the bloom.
- Title (string): Title of the bloom.
- Content (string): Description of the bloom.
- ImageUrl (string): URL of the image associated with the bloom.
- TotalLikes (int): A counter holding the number of likes of the bloom.
- UserId (int): Foreign key referencing the user who created the bloom.
- Date (DateTime): Date and time when the bloom was created.
Represents collections of Blooms.
- BoardId (int): Unique identifier for the board.
- Name (string): Title of the board.
- Note (string): Description of the board.
- UserId (int): Foreign key referencing the user who created the board.
Represent the associative relationship between blooms and boards.
- BloomBoardId (int): Unique identifier for the bloomboard.
- BoardId (int): Unique identifier for the board.
- BloomId (int): Unique identifier for the bloom.
- Date (DateTime): Date and time when the relationship was created.
Represents comments on Blooms.
- CommentId (int): Unique identifier for the comment.
- Content (string): Content of the comment.
- UserId (int): Foreign key referencing the user who created the comment.
- BloomId (int): Foreign key referencing the bloom the comment is associated with.
- Date (DateTime): Date and time when the comment was created.
Represents likes on Blooms.
- LikeId (int): Unique identifier for the like.
- UserId (int): Foreign key referencing the user who liked the bloom.
- BloomId (int): Foreign key referencing the bloom that was liked.
Handles user-related operations.
- Register(): Register a new user.
- Login(): Authenticate a user.
- Logout(): Log out the current user.
- UserProfile(int id): View a user's profile.
Handles bloom-related operations.
- Index(): View all blooms.
- Show(int id): View details of a specific bloom.
- New(): Create a new bloom.
- Previwe(Bloom bloom): Allows the user to see how a bloom would look like before posting it.
- Edit(int id): Edit an existing bloom.
- Delete(int id): Delete a bloom.
Handles board-related operations.
- Index(): View all boards.
- Show(int id): View details of a specific board.
- New(): Create a new board.
- Edit(int id): Edit an existing board.
- Delete(int id): Delete a board.
Handles comment-related operations.
- New(int bloomId): Create a new comment on a bloom.
- Delete(int id): Delete a comment.
Views in BoardBloom are responsible for rendering the user interface. Each controller action has a corresponding view that presents the data and allows user interaction. The views are implemented using Razor syntax.
The Data Access Layer (DAL) handles communication with the database. Entity Framework Core is used for Object-Relational Mapping (ORM), allowing for simple CRUD operations on the database.
namespace BoardBloom.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
public DbSet<Board> Boards { get; set; }
public DbSet<BloomBoard> BloomBoards{ get; set; }
public DbSet<Bloom> Blooms { get; set; }
public DbSet<Comment> Comments { get; set; }
public DbSet<Like> Likes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<BloomBoard>()
.HasKey(bb => bb.Id);
modelBuilder.Entity<BloomBoard>()
.HasOne(bb => bb.Bloom)
.WithMany(b => b.BloomBoards)
.HasForeignKey(bb => bb.BloomId);
modelBuilder.Entity<BloomBoard>()
.HasOne(bb => bb.Board)
.WithMany(b => b.BloomBoards)
.HasForeignKey(bb => bb.BoardId);
}
}
}
BoardBloom implements security measures to protect user data and ensure safe interactions:
- Authentication: Users must log in to access certain features.
- Authorization: Role-based access control to restrict actions to Admin or User roles.
- Data Protection: Sensitive data, such as passwords, is hashed and stored securely.
To deploy BoardBloom:
- Build the project in Visual Studio.
- Configure the database connection string in the appsettings.json file.
- Run database migrations to set up the database schema.
- Launch the application and verify all features are working correctly.
-
Example of ChatGPT-4 used to help with editing the layout of the app : https://chatgpt.com/share/bd5575a0-e540-4ae9-9089-208313fe3f0e
-
We also used Github Copilot for in editor code suggestions and completions and here are 2 examples:
-
ChaptGPT-4o for creating the UML Diagram for documentation: https://chatgpt.com/share/f4804ad3-87cd-4412-8f40-2be17e36e33e
Testing is crucial to ensure the reliability and performance of BoardBloom. Implement unit tests and integration tests to cover the following areas:
- Models: Validate data integrity and business logic.
- Controllers: Test all controller actions for expected behavior.
- Views: Ensure views render correctly with the right data.
- Security: Verify authentication and authorization mechanisms.
- Example Unit Test: BloomControllerTests
- Index Action Description
Tests if the Index action returns a ViewResult with the expected list of Bloom entities filtered by a search term. Code Snippet
[Fact]
public void Index_ReturnsViewResult_WithExpectedData()
{
// Arrange
var fakeBlooms = new List<Bloom>
{
new Bloom { Id = 1, Title = "Bloom 1", Content = "Content 1", TotalLikes = 10, UserId = "user1" },
new Bloom { Id = 2, Title = "Bloom 2", Content = "Content 2", TotalLikes = 20, UserId = "user2" }
}.AsQueryable();
var fakeLikes = new List<Like>
{
new Like { Id = 1, BloomId = 1, UserId = "user1" },
new Like { Id = 2, BloomId = 2, UserId = "user1" }
}.AsQueryable();
var mockBloomDbSet = CreateMockDbSet(fakeBlooms);
var mockLikeDbSet = CreateMockDbSet(fakeLikes);
_mockContext.Setup(c => c.Blooms).Returns(mockBloomDbSet.Object);
_mockContext.Setup(c => c.Likes).Returns(mockLikeDbSet.Object);
var queryCollection = new QueryCollection(new Dictionary<string, Microsoft.Extensions.Primitives.StringValues>
{
{ "search", "Bloom" },
{ "page", "1" }
});
var mockHttpContext = new Mock<HttpContext>();
mockHttpContext.Setup(c => c.Request.Query).Returns(queryCollection);
_controller.ControllerContext = new ControllerContext
{
HttpContext = mockHttpContext.Object
};
_mockUserManager.Setup(um => um.GetUserId(It.IsAny<ClaimsPrincipal>())).Returns("user1");
// Act
var result = _controller.Index() as ViewResult;
// Assert
Assert.NotNull(result);
Assert.IsType<ViewResult>(result);
var viewDataBloomsEnumerable = result?.ViewData["blooms"] as IEnumerable<Bloom>;
var viewDataBlooms = viewDataBloomsEnumerable?.AsQueryable();
Assert.NotNull(viewDataBlooms);
Assert.Equal(fakeBlooms.Count(), viewDataBlooms?.Count());
}
- Show Action Description
Tests if the Show action returns a ViewResult containing the correct Bloom for the provided ID.
[Fact]
public void Show_ReturnsViewResult_WithBloom()
{
// Arrange
var fakeLikes = new List<Like>
{
new Like { Id = 1, BloomId = 1, UserId = "user1" }
}.AsQueryable();
var bloom = new Bloom
{
Id = 1,
Title = "Bloom 1",
Content = "Content 1",
UserId = "user1",
Likes = fakeLikes.ToList()
};
var mockBloomDbSet = CreateMockDbSet(new List<Bloom> { bloom }.AsQueryable());
_mockContext.Setup(c => c.Blooms).Returns(mockBloomDbSet.Object);
var mockLikesDbSet = CreateMockDbSet(fakeLikes.AsQueryable());
_mockContext.Setup(c => c.Likes).Returns(mockLikesDbSet.Object);
var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, "user1")
}, "mock"));
_mockUserManager.Setup(um => um.GetUserId(user)).Returns("user1");
_controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext { User = user }
};
// Act
var result = _controller.Show(1) as ViewResult;
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsType<Bloom>(viewResult.Model);
Assert.Equal("Bloom 1", model.Title);
}
- New Action Description
Tests if the New action returns a ViewResult with an empty Bloom model. Code Snippet
[Fact]
public void New_ReturnsViewResult_WithEmptyBloom()
{
// Act
var result = _controller.New() as ViewResult;
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsType<Bloom>(viewResult.Model);
Assert.Null(model.Title);
Assert.Null(model.Content);
}
- Edit Action (Authorized User) Description
Tests if the Edit action returns a ViewResult with the correct Bloom when accessed by an authorized user. Code Snippet
[Fact]
public void Edit_ReturnsViewResult_WithBloomForAuthorizedUser()
{
// Arrange
var bloom = new Bloom { Id = 1, Title = "Bloom 1", Content = "Content 1", UserId = "user1" };
_mockContext.Setup(db => db.Blooms.Find(1)).Returns(bloom);
var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, "user1")
}, "mock"));
_mockUserManager.Setup(um => um.GetUserId(user)).Returns("user1");
_controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext { User = user }
};
// Act
var result = _controller.Edit(1) as ViewResult;
// Assert
var viewResult = Assert.IsType<ViewResult>(result);
var model = Assert.IsType<Bloom>(viewResult.Model);
Assert.Equal("Bloom 1", model.Title);
}
- Edit Action (Unauthorized User) Description
Tests if the Edit action returns a redirect to the home page when accessed by an unauthorized user. Code Snippet
[Fact]
public void Edit_ReturnsRedirectToHomeForUnauthorizedUser()
{
// Arrange
var bloom = new Bloom { Id = 1, Title = "Bloom 1", Content = "Content 1", UserId = "user2" };
_mockContext.Setup(db => db.Blooms.Find(1)).Returns(bloom);
var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, "user1")
}, "mock"));
_mockUserManager.Setup(um => um.GetUserId(user)).Returns("user1");
_controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext { User = user }
};
// Act
var result = _controller.Edit(1) as RedirectToActionResult;
// Assert
Assert.IsType<RedirectToActionResult>(result);
Assert.Equal("Index", result.ActionName);
Assert.Equal("Home", result.ControllerName);
}
- Delete Action (Unauthorized User) Description
Tests if the Delete action returns a redirect to the home page when accessed by an unauthorized user. Code Snippet
[Fact]
public void Delete_ReturnsRedirectToHomeForUnauthorizedUser()
{
// Arrange
var bloom = new Bloom { Id = 1, Title = "Bloom 1", Content = "Content 1", UserId = "user2" };
_mockContext.Setup(db => db.Blooms.Find(1)).Returns(bloom);
var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, "user1")
}, "mock"));
_mockUserManager.Setup(um => um.GetUserId(user)).Returns("user1");
_controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext { User = user }
};
// Act
var result = _controller.Delete(1) as RedirectToActionResult;
// Assert
Assert.IsType<RedirectToActionResult>(result);
Assert.Equal("Index", result.ActionName);
Assert.Equal("Home", result.ControllerName);
}
- Delete Action (Authorized User) Description
Tests if the Delete action removes the specified Bloom and returns a redirect to the home page for an authorized user. Code Snippet
[Fact]
public void Delete_RemovesBloomAndReturnsRedirectForAuthorizedUser()
{
// Arrange
var bloom = new Bloom { Id = 1, Title = "Bloom 1", Content = "Content 1", UserId = "user1" };
_mockContext.Setup(db => db.Blooms.Find(1)).Returns(bloom);
var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, "user1")
}, "mock"));
_mockUserManager.Setup(um => um.GetUserId(user)).Returns("user1");
_controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext { User = user }
};
// Act
var result = _controller.Delete(1) as RedirectToActionResult;
// Assert
Assert.IsType<RedirectToActionResult>(result);
Assert.Equal("Index", result.ActionName);
Assert.Equal("Home", result.ControllerName);
_mockContext.Verify(db => db.Blooms.Remove(bloom), Times.Once);
_mockContext.Verify(db => db.SaveChanges(), Times.Once);
}
- Create Action Description
Tests if the Create action successfully adds a new Bloom and redirects to the Index action. Code Snippet
csharp
[Fact]
public void Create_AddsBloomAndReturnsRedirectToIndex()
{
// Arrange
var bloom = new Bloom { Title = "New Bloom", Content = "New Content", UserId = "user1" };
var user = new ClaimsPrincipal(new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, "user1")
}, "mock"));
_mockUserManager.Setup(um => um.GetUserId(user)).Returns("user1");
_controller.ControllerContext = new ControllerContext
{
HttpContext = new DefaultHttpContext { User = user }
};
// Act
var result = _controller.Create(bloom) as RedirectToActionResult;
// Assert
Assert.IsType<RedirectToActionResult>(result);
Assert.Equal("Index", result.ActionName);
_mockContext.Verify(db => db.Blooms.Add(It.IsAny<Bloom>()), Times.Once);
_mockContext.Verify(db => db.SaveChanges(), Times.Once);
}