Skip to content

Chapter 4: Backend Design Document - Beta

Data Modeling

User

Purpose: To provide an identity for user account and information

Principle: Stores credential and username

States:

tsx
registered: set User
username, password: registered -> one String
registered: set User
username, password: registered -> one String

Profile [User]

Purpose: To store the user’s personal information

Principle: Each user has their initial profile when they registered. The profile can be updated. And will be deleted after user delete their account.

State:

tsx
nickname: one String
email: one String
dateJoined: one int (timestamp)
headshotUrl: one String
identity: one String
role: one int
region: set Float
liked: set Item (Post/Reply)
favorite: set Item (Post/Reply)
nickname: one String
email: one String
dateJoined: one int (timestamp)
headshotUrl: one String
identity: one String
role: one int
region: set Float
liked: set Item (Post/Reply)
favorite: set Item (Post/Reply)

Session [User]

Purpose: Manage and maintain the state of a user in the system over t period.

Principle: A session begins when a user login. Session maintains state s for time t the session will expire after time t. after a session starts (and before it ends), the getUser action returns the user identified at the start: start (u, s); getUser (s, u.)

State:

tsx
active: set Session
user: active -> one User
active: set Session
user: active -> one User

Relationship [User]

Purpose: To define and manage the connection states between users

Principle: The relationship has directional states. It will take a set of User object and record their relationship states.

State:

tsx
Rel : Relationship -> set String // define the relationship as a set
u1, u2: Rel -> set User // assign relationship between u1 and u2, the u1 is the origin, u2 is the target. u1 -> u2 forms a directional relationship
relationship: Rel -> set String // the type of relationship between two users  can be follower, friend to partner, to activate different functions.
Rel : Relationship -> set String // define the relationship as a set
u1, u2: Rel -> set User // assign relationship between u1 and u2, the u1 is the origin, u2 is the target. u1 -> u2 forms a directional relationship
relationship: Rel -> set String // the type of relationship between two users  can be follower, friend to partner, to activate different functions.

Map

Purpose: To provide a visual representation of geographic data and allow users to interact with it. It also serves as a base to display location based informations, such as markers.

Principle: On mobile platforms, users can use touch gestures to interact with maps. On desktop, they can use mouse. Map have multiple layers and can change themes (provided by api). Map can display markers on its geographical locations.

State:

tsx
centerPoint: set Float ((set [lng, lat]))
zoomLevel: Float
layers: set String (Collections of data visualized on the map (e.g., flower field))
markers: Symbols or icons indicating points of interest or events.
theme: Visual style (e.g., satellite, terrain, nighttime).
centerPoint: set Float ((set [lng, lat]))
zoomLevel: Float
layers: set String (Collections of data visualized on the map (e.g., flower field))
markers: Symbols or icons indicating points of interest or events.
theme: Visual style (e.g., satellite, terrain, nighttime).

Marker [User]

Purpose: To display specific points of interest, events, or user locations on the map.

Principle: The Marker can be interacted and expanded into a larger information card. The Marker has different types, now including User, and POI points of interest. If the density of the marker becomes too large, they can be clustered into one marker, which will show a list by activation.

State:

tsx
location: set Float
type: one String (User/POI)
cluster: set User
location: set Float
type: one String (User/POI)
cluster: set User

Favorite[User, Item]

Purpose: Save articles and answers for later review. It will also boost the mass voting system.

Principle: The user can click favorite from a list, and jump to the corresponding article. They can also delete their favorite item.

State:

tsx
items: set String
user: set String
favoriteCount: int
favoriteList: set String
items: set String
user: set String
favoriteCount: int
favoriteList: set String

Like[User, Item]

Purpose: A mass-voting mechanism to identify good quality contents from the community

Principle: User can upvote by clicking a button next to a post/answer. Good quality contents will raise to the top of the answer list

State:

tsx
items: one String
users: set String
likeCount: one int
items: one String
users: set String
likeCount: one int

Tag[User, Item]

Purpose: a general parameter to aid classification of posts, replies and users

Principle: A tag can be assigned to multiple items. An item can have multiple tags. Required tags can’t be deleted, because it will be necessary for classification purpose. Normal tags can be deleted.

The "Tag" concept contains two documents: a list storing the tags and its related

State:

tsx
name: Tag -> one String // Users who own this tag
owns: Item -> set Tag // Items which this tag is assigned to
name: Tag -> one String // Users who own this tag
owns: Item -> set Tag // Items which this tag is assigned to

Post [User]

Purpose: Post is the core concept of this app. It represents a general rich text message that applicable to use as article, question and knowledge(for optional function wiki)

Principle: Posts are created using editing tools in each functions. Posts can be edited or delete by the user, but can’t be edit by other user. Posts have visibility options. By default, it is set to public, meaning all users can view. It can also be set as private, friends-only or partner-only.

State:

tsx
author: one User
title: one String
content: one String
visibility: one String // public, private, friendsOnly, partnersOnly
postType: one String // article, question
author: one User
title: one String
content: one String
visibility: one String // public, private, friendsOnly, partnersOnly
postType: one String // article, question

Reply [User, Post]

Purpose: Reply contains two user cases: comment on a post, or answer on a question. It depends on a post to exist.

Principle: Replies can only be created after a post. It can’t independently exist. Only registered user can reply a post. Reply uses private parameter to identify types. Like Post, Reply can also be favorited or liked by users.

State:

tsx
author: one User
// title: one String // reply doesn't need title
content: one String
relatedPost: one String
visibility: one String // public, private, friendsOnly, partnersOnly
replyType: one String // article, question
author: one User
// title: one String // reply doesn't need title
content: one String
relatedPost: one String
visibility: one String // public, private, friendsOnly, partnersOnly
replyType: one String // article, question

App Definition

Write a definition of your app as a list of concepts, with type parameters instantiated appropriately, and draw a diagram (like those we looked at in lecture) showing the state of the entire app, with contours showing which subdiagrams belong to which concepts.

tsx
app WeeHive
concepts
	User
	Profile [User.User]
	Session [User.User]
	Relationship [User1.User, User2.User]
	Post [User.User]
	Reply [Post.Post, User.User]
	Tag [Post.Post / User.User] // how to define a concept that can be applied to both?
	Favorite [Post.Post]
	Like [Post.Post]
	Map [?User.User]
	Marker[User.User] // to display the sentiment calculated from reactions for each post.

concept syncronize
Sync createUser(username: String, password: String, out user: User)
    user = User.create(username, password)
    profile = Profile.createForUser(user)

Sync authenticateUser(username: String, password: String, out session: Session)
    user = User.verifyCredentials(username, password)
    session = Session.initiateForUser(user)

Sync addReplyToPost(author: ObjectId, content: String, replyType: ReplyType, postId: ObjectId, options?: ReplyOptions)
    reply = Reply.addToPost(author, content, replyType, postId, options)

Sync fetchRepliesByPostId(postId: ObjectId)
    replies = Reply.listByPostId(postId)

Sync createNewTag(name: String)
    tag = Tag.initialize(name)

Sync associateTagWithItem(tagId: ObjectId, itemId: ObjectId)
    Tag.linkToItem(tagId, itemId)

Sync fetchTagsByItemId(itemId: ObjectId)
    tags = Tag.listByItemId(itemId)

Sync initiateFollow(userInitiator: ObjectId, userTarget: ObjectId)
    Relationship.follow(userInitiator, userTarget)

Sync sendFriendRequest(initiator: ObjectId, recipient: ObjectId)
    Relationship.proposeFriendship(initiator, recipient)

Sync sendRequest(initiator: ObjectId, recipient: ObjectId)
    if Relationship.verifyFriendship(initiator, recipient)
        Relationship.proposePartnership(initiator, recipient)

Sync acceptRequest(sender: ObjectId, receiver: ObjectId)
    Relationship.acceptRequest(sender, receiver)

Sync declineRequest(sender: ObjectId, receiver: ObjectId)
    Relationship.declineRequest(sender, receiver)

Sync removeRelationship(user1: ObjectId, user2: ObjectId, relationType: RelType)
    Relationship.removeRelationship(user1, user2, relationType)

Sync getRelationships(userId: ObjectId, relationType: RelType)
    relationships = Relationship.getRelationships(userId, relationType)

Sync tagPost(targetPost: Post, tagContent: String, out tag: Tag)
	Tag.createForPost(targetPost, tagContent, tag)

Sync tagUser(targetUser: User, tagContent: String, out tag: Tag)
	Tag.createForUser(targetUser, tagContent, tag)

Sync favoritePost(user: User, targetPost: Post, out favorite: Favorite)
	Favorite.create(user, targetPost, favorite)

Sync likePost(user: User, targetPost: Post, out like: Like)
	Like.create(user, targetPost, like)

Sync CreteMarkerOnMap(post: Post, sentimentData: any, out marker: Marker)
Marker.createForPost(post, sentimentData, marker)

Sync updateMarker(user: User, locationData: any, out map: Map)
	Map.updateMap(user, locationData, map)
app WeeHive
concepts
	User
	Profile [User.User]
	Session [User.User]
	Relationship [User1.User, User2.User]
	Post [User.User]
	Reply [Post.Post, User.User]
	Tag [Post.Post / User.User] // how to define a concept that can be applied to both?
	Favorite [Post.Post]
	Like [Post.Post]
	Map [?User.User]
	Marker[User.User] // to display the sentiment calculated from reactions for each post.

concept syncronize
Sync createUser(username: String, password: String, out user: User)
    user = User.create(username, password)
    profile = Profile.createForUser(user)

Sync authenticateUser(username: String, password: String, out session: Session)
    user = User.verifyCredentials(username, password)
    session = Session.initiateForUser(user)

Sync addReplyToPost(author: ObjectId, content: String, replyType: ReplyType, postId: ObjectId, options?: ReplyOptions)
    reply = Reply.addToPost(author, content, replyType, postId, options)

Sync fetchRepliesByPostId(postId: ObjectId)
    replies = Reply.listByPostId(postId)

Sync createNewTag(name: String)
    tag = Tag.initialize(name)

Sync associateTagWithItem(tagId: ObjectId, itemId: ObjectId)
    Tag.linkToItem(tagId, itemId)

Sync fetchTagsByItemId(itemId: ObjectId)
    tags = Tag.listByItemId(itemId)

Sync initiateFollow(userInitiator: ObjectId, userTarget: ObjectId)
    Relationship.follow(userInitiator, userTarget)

Sync sendFriendRequest(initiator: ObjectId, recipient: ObjectId)
    Relationship.proposeFriendship(initiator, recipient)

Sync sendRequest(initiator: ObjectId, recipient: ObjectId)
    if Relationship.verifyFriendship(initiator, recipient)
        Relationship.proposePartnership(initiator, recipient)

Sync acceptRequest(sender: ObjectId, receiver: ObjectId)
    Relationship.acceptRequest(sender, receiver)

Sync declineRequest(sender: ObjectId, receiver: ObjectId)
    Relationship.declineRequest(sender, receiver)

Sync removeRelationship(user1: ObjectId, user2: ObjectId, relationType: RelType)
    Relationship.removeRelationship(user1, user2, relationType)

Sync getRelationships(userId: ObjectId, relationType: RelType)
    relationships = Relationship.getRelationships(userId, relationType)

Sync tagPost(targetPost: Post, tagContent: String, out tag: Tag)
	Tag.createForPost(targetPost, tagContent, tag)

Sync tagUser(targetUser: User, tagContent: String, out tag: Tag)
	Tag.createForUser(targetUser, tagContent, tag)

Sync favoritePost(user: User, targetPost: Post, out favorite: Favorite)
	Favorite.create(user, targetPost, favorite)

Sync likePost(user: User, targetPost: Post, out like: Like)
	Like.create(user, targetPost, like)

Sync CreteMarkerOnMap(post: Post, sentimentData: any, out marker: Marker)
Marker.createForPost(post, sentimentData, marker)

Sync updateMarker(user: User, locationData: any, out map: Map)
	Map.updateMap(user, locationData, map)

Untitled

Concept Implementation Reflections

1. Concept Reduction and Modularization

2. Reduce with backend calculation

Many parameters can be calculated through backend operations. Therefore it's not necessary to store the values in separate documents. For example, when I was implementing tag concept, I was debating whether to include a list document to store all the tag names as a separate document. Eventually I realized I can get all the tags as a list by mongodb queries for the users to select. Similar situations applies to Like concept. We don't need to store and update the like counts in the database. We can just calculate it in real time as a database query. However, I don't know if it will take be less efficiency when the scale of dataset grows too big.

Consistant return formatting: I found it is important to keep the backend return a consistant format, to make frontend developers' life easier. In this assignment, I tried to make every return follow the format of:

```
{
	msg: "message of the current operation and its related ids",
	return: "returning values"
}
```

Design refinements of key concepts

  1. Profile is inherently similar to posts, but it has different content format and doesn’t require reply. The key difference between “post” and “profile” is one user can only have one profile but multiple posts. One remaining question is whether to keep the profile belongs to user. The TAs said RESTFul principle requires each concept to be independent. I don't know if the profile should be independent when designing the routes.

  2. Relationship is an extended “Friend” concept. In my app, a relationship has 4 states: no relationship, one direction follow (no need for approval), two-directional friendship, and two-directional partnership. When two user follow each other, they automatically become friends. But a partner request still need two users becomes friends.

During implementation, I found generalizing a concept may bring additional complexity to one concept design, but it will in turn make the program structure nicer. It is a trade-off between designing one concept that can be applied to multiple use cases. For this concept, I tried to make one "relationship" concept to handle user relationships, instead of separate the follow with friendship, though their behavior may be different. That's to reduce the complexity of the documents and make it easier to maintain.

  1. Map is a concept I eventually understand during implementation. I was always doubting the necessity of it because it is a front-end heavy feature. I found the goal of making a concept for it is to store the state of the map in the database. So the next time when user come back, they can use the same settings and zoom levels, for a smoother user experience. That's also a great lession that the user experience is also relies on good design of backend side.

Collaboration

I used ChatGPT to help me debug and guidance for some tricky codes.

Untitled

Vercel Deployment Link: 61040-backend.vercel.app

RESTful routes: https://github.com/srcJin/61040-backend/blob/main/server/routes.ts -->