My Neighborkhood
My Neighborkhood is a fullstack front-end to back-end project. It is a social interaction map focused on meeting the community through your dog.
- Live Link Address: https://my-neighborkhood.herokuapp.com/
- Project Github: https://github.com/earlwoo/My-Neighborkhood
MVP
- Ability to create new users and have user login with authorization
- Users can edit their own or their pet's info
- Ability to login as a demo user with access to website
- Users can view a map of Neighborks whom they can chat with
- Users can create, update, delete a message in chat with other user
- Users can search through all neighbors
BONUS / STRETCH GOALS
- Search
- Events
- Dog specific map markers
- doggy day care
- pet sitters
- parks
- events
- Adoption listings by neighborhoods
TECHNOLOGIES USED
- Python
- Flask
- SQLAlchemy
- Docker
- PSQL Database
- CSS
- JSX
- React
- Redux
- Heroku
ENVIORNMENT DEPENDENCIES/PACKAGES
- Google Maps React
- Socket-IO
- React Geocode
- Chakra UI
- wtforms
- React Icons
- Font Awesome
- Faker
DATABASE SCHEMA
Technical Showcase
To prevent multiple sockets for chat rooms, a user is passed into a state set by useState to open a chat socket only when a user is selected.
<Flex flexDirection="column" alignItems="flex-start">
{Object.values(chats).map(chat => (
<Box
as="button"
onClick={() => setShow(chat)}
key={chat.id}
fontWeight="semibold" letterSpacing="wide"
fontSize="xs" marginBottom="10px" padding="10px" >
<Avatar src={Object.values(chat.users)[1].avatar}></Avatar>
<Box fontSize="14" paddingLeft="5" fontWeight="semibold" as="button" >{Object.values(chat.users)[1].name}
</ Box>
<Divider paddingTop="1" maxWidth="275px"/>
</Box >
))}
<Portal >
{show.id && <MessageModal setShow={setShow} ref={ref} chat={show} user={user} />}
</Portal>
</Flex>
Python objects are converted to readable info when sent from the back-end to front-end using a to_dict method for applicable class instances. Along with various properties through table relations.
pet = db.relationship(
"Pet", uselist=False,
back_populates="owner"
)
chats = db.relationship(
'Chat',
secondary=user_chats,
back_populates='users'
)
def to_dict(self):
return {
"id": self.id,
"firstname": self.firstname,
"lastname": self.lastname,
"email": self.email,
"avatar": self.avatar,
"address": json.loads(self.address),
"location": json.loads(self.location),
"bio": self.bio,
"pet": self.pet.to_dict(),
"chats": {chat.id: chat.to_dict() for chat in self.chats}
}
Websocket on the frontend: The useEffect()
enables a new chat
socket to be opened. We used the chat prop as an identifier for when we interact with the backend of the chat
socket. When the selected chat changes, current socket chat
socket is closed, and then reopens with the next chat passed in as a prop. Upon submission of a new message, we emit the message to the backend socket and sends the data to create and store the message into the database.
useEffect(() => {
socket = io();
socket.on(chat.id, (data) => {
setMessages(messages => [...messages, data])
})
return (() => {
socket.disconnect()
})
}, [messages.length, chat])
const sendChat = (e) => {
e.preventDefault()
// check for user credential
if (chatInput.length > 0) {
socket.emit("chat", {
user_id: user.id,
chat_id: chat.id,
body: chatInput,
created_at: new Date().toGMTString(),
updated_at: new Date().toGMTString()
})
}
setChatInput("")
}
- Websocket on the backend: There's one socket that receives all
chat
events. After adding the emitted message to the database, weemit
back to the frontend with additional data such aschat_id
. Thatchat_id
will be used to determine which frontend channel'schat
socket we should broadcast the new message to. The data sent from the backend will now be received as the variablechat
in theuseEffect()
.
app/socketIO.py
@socketio.on("chat")
def handle_chat(data):
new_message = Message(
user_id=data['user_id'],
chat_id=data['chat_id'],
body=data['body'],
created_at=data['created_at'],
updated_at=data['updated_at']
)
db.session.add(new_message)
db.session.commit()
messages = Message.query.filter(Message.user_id == data['user_id'], Message.body == data['body']).all()
ourMsg = messages[len(messages) - 1]
data['id'] = ourMsg.id
emit(data["chat_id"], data, broadcast=True)
TABLE USERS
- id (integer, primary key, not null)
- firstName (string, not null)
- lastName (string, not null)
- email (string, unique, not null)
- hashedPassword (string, not null)
- address (string, unique, not null)
- avatar (string)
- bio (text, not null)
- location (text, not null)
- created_at (dateTime, not null)
- updated_at (dateTime, not null)
TABLE PETS
- id (integer, primary key)
- name (string, not null)
- image (string)
- owner_id (integer, not null, foreign key)
- bio (text)
- age (string )
- created_at (dateTime, not null)
- updated_at (dateTime, not null)
TABLE CHATS
- id (integer, primary key, not null)
- name (string, not null)
- created_at (dateTime, not null)
- updated_at (dateTime, not null)
TABLE MESSAGES
- id (integer, primary key, not null)
- user_id (integer, not null, foreign key)
- chat_id (integer, not null, foreign key)
- body (text, not null)
- created_at (dateTime, not null)
- updated_at (dateTime, not null)
TABLE USER CHATS
- id (integer, primary key, not null)
- user_id (integer, not null, foreign key)
- chat_id (integer, not null, foreign key)
- created_at (dateTime, not null)
- updated_at (dateTime, not null)
BACKEND ROUTES
auth
- GET /api/auth/ - Authenticate
- POST /api/auth/login - Log in
- POST /api/auth/signup - Sign up
- DELETE /api/auth/logout - Log Out
users
- GET /:id - Get specific user
- PATCH /:id - Edit user bio
- PATCH /pet - Edit user pet
- POST /api/users - Sign Up
all_users
- GET /api/all_users/ - Get all user profile
chats
- GET /api/chats/ - Get all subscribed chats
- POST /api/chats/ - Create a chat
messages
- GET /api/messages/:chatId - Fetch all messages for given chat
- PATCH /api/messages/:messageId - Edit a specific message
- DELETE /api/messages/:messageId - Delete a message
PROJECT CHALLENGES
* Figuring out how to make the least amount of calls to the database and utilizing the redux state for data
* Deciding which components have access to which slice of state and how to make the data accessible to other components by either props, or subscribing to the store
* The re-rendering nature of React, and understanding how those re-renders affect the entire components structure, while also being mindful of what causes the re-render