Skip to content

Task 3: Data Modeling

Concepts

Concept: User

Purpose: Authenticate Users
Principle: After a user registers with a username and password, they can authenticate as that user by providing a matching username and password
State:

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

Actions:

register (n, p: String, out u: User)
authenticate (n, p: String, out u: User)

Concept: Session [User]

Purpose: Authenticate user for extended period
Principle: after a session starts (and before it ends), the getUser action returns the user identified at the start
State:

active: set Session
user: active -> one User

Actions:

start (u: User, out: s: Session)
getUser (s: Session, out u: User)
end (s: Session)

Concept: Post [User]

Purpose: Display content
Principle: A user can start a post, add content of their liking, which can include text, images, etc., and submit the post for (allowed) users to view
State:

posts: set Post
content: posts -> one String
author: posts -> one User
dateCreated: posts -> one Date
restrictedUsers: posts -> set User

Actions:

create (c: String, u: User, out p: Post)
delete (p: Post)
update (p: Post, c: String)
getPostsByAuthor (a: User, out p: set Post)
isAuthor (p: Post, u: User)
removeRestrictedUser (p: Post, u: User)
addRestrictedUser (p: Post, u: User)
canView (p: Post, u: User)

Concept: Friend [User]

Purpose: Allows users to send each other content
Principle: When a user publishes a post, it will be sent to all of their friends. They can also send content to their friends in other ways, such as chatting
State:

friendships: one Friendship
user1, user2: friendships -> one User
friendRequests: set FriendRequest
from, to: friendRequests -> one User
status: friendRequests -> one String

Actions:

getRequests(user: User, out f: set FriendRequests)
sendRequest (from: User, to: User)
acceptRequest (from: User, to: User)
rejectRequest(from: User, to: User)
removeRequest(from: User, to: User)
removeFriend(u: User, friend: user)
getFriends(u: User, out f: set User)
isFriend(u: User, f: User)

Concept: Bookmark [User]

Purpose: Directly navigates a user to a particular page on the site
Principle: The user navigates to a page, bookmarks the page, and can later select that bookmark to navigate to the page in one step
State:

bookmarks: set Bookmark user: bookmarks -> set User name, destination: bookmarks -> one String

Actions:

create (u: User, name: String, destination: String, out b: Bookmark)
delete (b: Bookmark)
rename (b: Bookmark, name: String)
getByUser(u: User, out b: set Bookmark)
isOwner(b: Bookmark, u: User)

Concept: Gathering [User] [Post]

Purpose: Incentivize users to get together in-person
Principle: A user can start a Gathering, add details about the get-together, and invite other users for them to accept or decline the invitation. Acceptors can submit only one post to the gathering and view other acceptors’ posts submitted to the gathering.
State:

gatherings: set Gathering
title, description: gatherings -> one String
creator: gatherings -> one User
hosts, acceptors: gatherings -> set User
posts: gatherings -> set Post
canceled: gatherings -> one Bool
invites: set Invite
gathering: invites -> one Gathering
from, to: invites -> one User
status: invites -> one String

Actions:

create (t: String, c: String, u: User, out g: Gathering)
delete (g: Gathering)
cancel (g: Gathering)
update (g: Gathering, t: String, c: String)
addHosts (g: Gathering, u: set User)
addAcceptor (g: Gathering, u: User)
removeAcceptor(g: Gathering, u: User)
isHost(g: Gathering, u: User)
isInvited(g: Gathering, u: User)
isAcceptor(g: Gathering, u: User)
invite (g: Gathering, from: User, to: User)
acceptInvite (g: Gathering, to: User)
declineInvite (g: Gathering, to: User)
addPost (g: Gathering, p: Post)
removePost (g: Gathering, p: Post)

Concept: Message [User]

Purpose: Incentivizes users to send one meaningful message when communicating with other users
Principle: A user creates a message and sends it to another user, only one can be sent daily and the message cannot be edited. The receiver can delete the message, or respond to it by sending one back
State:

messages: set Message
content: messages -> one String
dateCreated: messages -> one Date
from, to: messages -> one User

Actions:

create (from: User, to: User, c: String, out m: Message)
getBySender (u: User)
getByReceiver (u: User)

Synchronizations

App Minimaloo

include User
include Session[User]
include Post[User]
include Friend[User]
include Bookmark[User]
include Gathering[User][Post]
include Message [User]

sync login (username: String, password: String, out s: Session)
  when User.authenticate(username, password, out user)
  Session.start(user, out session)

sync logout (s: Session)
  Session.end(s)

sync createPost (c: String, u: User)
  Post.create(c, u, out p)\

sync deletePost (post: Post, n: String, p: String)
  when User.authenticate(n, post, out u)
  Post.isAuthor(post, u)
  Post.delete(post)

sync updatePost (post: Post, c: String, u: User)
  when Post.isAuthor(post, u)
  Post.update(post, c)

sync getPosts (author: User, u: User, out p: set Post)
  when (Friend.isFriend(author, u) && Post.canView(p, u)) || author = u
  Post.getPostsByAuthor(author, out p)

sync restrictPostAccess (p: Post, u: User, a: User)
  when Post.isAuthor(p, a)
  Post.addRestrictedUser(p, u)

sync allowPostAccess (p: Post, u: User, a: User)
  when Post.isAuthor(p, a)
  Post.removeRestrictedUser(p, u)

sync sendFriendRequest (from: User, to: User)
  Friend.sendRequest(from, to)

sync acceptFriendRequest (from: User, to: User)
  Friend.acceptRequest(from: User, to: User)

sync rejectFriendRequest (from: User, to: User)
  Friend.rejectRequest(from, to)

sync removeFriendRequest (from: User, to: User)
  Friend.removeRequest(from, to)

sync getFriends(u: User, out f: set User)
  Friend.getFriends(u, out f)

sync createBookmark(n: String, d: URL, u: User)
  Bookmark.create(u, n, d, out b)

sync deleteBookmark(b: Bookmark)
  Bookmark.delete(b)

sync updateBookmark(b: Bookmark, n: String, d: URL)
  Bookmark.update(b, n, d)

sync createGathering(t: String, c: String, u: User, hosts: set User, invitees: set User)
  when Gathering.create(t, c, u, out g)
  Gathering.addHosts(g, hosts)
  for (i of invitees) Gathering.invite(g, u, i)

sync deleteGathering(g: Gathering, u: User)
  when Gathering.isHost(g, u)
  Gathering.delete(g)

sync addGatheringHosts(g: Gathering, u: set User, h: User)
  when Gathering.isHost(g, h)
  Gathering.addHosts(g, u)

sync inviteToGathering(g: Gathering, invitees: set User, h: User)
  when Gathering.isHost(g, h)
  for (i in invitees) Gathering.invite(g, h, i)

sync acceptInvitation(g: Gathering, u: User)
  when Gathering.isInvited(g, u)
  Gathering.acceptInvite(g, u)

sync declineInvitation(g: Gathering, u: User)
  when Gathering.isInvited(g, u)
  Gathering.declineInvite(g, u)

sync createPostForGathering(g: Gathering, c: String, u: User)
  when Gathering.isAcceptor(g, u)
  Post.create(c, u, out p)
  Gathering.addPost(g, p)

sync associatePostToGathering(g: Gathering, p: Post, u: User)
  when Post.isAuthor(p, u)
  Gathering.isAcceptor(g, u)
  Gathering.addPost(g, p)

sync disassociatePostFromGathering(g: Gathering, p, Post, u: User)
  when Post.isAuthor(p, u) || Gathering.isHost(g, u)
  Gathering.removePost(g, p)

sync sendMessage(c: String, from: User, to: User)
  Message.create(from, to, c, out m)

sync getSentMessages(u: User, out m: set Messages)
  Message.getBySender(u, out m)

sync getReceivedMessages(u: User, out m: set Messages)
  Message.getByReceiver(u, out m)

Diagram

Global Data Model


Task 4: Data Representation

Concept Representations


Task 5 & 6: RESTful Routes & Synchronizations

Server Routes

Front-end Interface


Task 7: Deployment

Deployment to Vercel


Task 8: Design Reflection

User access to content, such as posts, greatly influenced the redesigns of my original concepts when implementing the backend. Originally, I was planning to have users control the access to their content through a feed in my original Friend concept and a ViewingRestrictions concept. The Friend concept allowed users who were friends to initially see each other’s content, and ViewingRestrictions allowed them to refine the access controls. ViewingRestrictions would have contained every content item, like a post, its owner, and which users were prohibited from viewing it. Since I was also using ItemTimer and TemporaryStorage concepts for the feed, it made the synchronizations very complex. To simplify the implementation, I switched back to the default Friend implementation, removed the feed state, and got rid of ItemTimer, TemporaryStorage, ViewingRestrictions. Instead, I made posts track their own access with a list of restricted users, which only the author can modify. When the user wants to view posts, the backend first queries its friends and then for each queries posts that are not restricted from the user.

In retrospect, I think I would have kept the ViewingRestrictions concept, which would have separated access and made it easier to manage more items with access control, like posts and gatherings, and not tying it to each concept individually. I believe I mistakenly thought it would be more tedious to implement individually in the context of trying to implement the feed.

When implementing my API routes, I ended up grouping multiple synchronizations under the same route, primarily those that update the concept states. Many of the original synchronizations were for specific updates, but I realized it was much easier to handle one update route and have the user use the UI to perform updates to specific fields.


Project Repository

Entire Project Repository