Problem: The difficulty for developers to create social media features within their applications.
Solution: My API provides a comprehensive set of endpoints and functionalities to enable developers to easily integrate social media features into their applications. This includes user profiles, post creation and management, commenting and liking, following and unfollowing, and messaging.
This project utilised GitHub Projects to manage the project. Between September 8th and 15th, the project was created, and initial planning was completed.
While working on the project I realised that some features that I was initially planning to create would be far too complex and outside of the scope of the project, as such, I decided instead to focus more on other parts of the project to enhance their functionality.
The ERD illustrates the draft of the relationships between entities in the database. During the development process, the ERD was modified to reflect changes in the entities, and relationships.
id
, username
, email
, password_hash
, bio
, is_admin
, is_confirmed
, confirmed_on
id
, user_id
, content
, created_at
, updated_at
id
, user_id
, post_id
, content
, created_at
, updated_at
id
, user_id
, post_id
, created_at
id
, follower_id
, followed_id
User
has many Posts
, Comments
, and Likes
.Post
belongs to a User
and can have many Comments
and Likes
.Comment
belongs to a User
and a Post
.Like
belongs to a User
and a Post
.Follow
connects two Users
(follower and followed).Comment
must always belong to a User
and a Post
.post.comments
relationship.User
is deleted, their associated Posts
, Comments
, and Likes
can be automatically deleted.GET /users
page
(optional): Page number for paginationper_page
(optional): Number of users per pageAuthorization
header.curl -H "Authorization: Bearer <your_token>" http://localhost:5000/users
[
{
"id": 1,
"username": "user1",
"email": "user1@example.com",
"profile_picture": "https://example.com/profile1.jpg",
"bio": "This is user 1."
},
{
"id": 2,
"username": "user2",
"email": "user2@example.com",
"profile_picture": "https://example.com/profile2.jpg",
"bio": "This is user 2."
}
]
GET /users/{user_id}/profile
HTTP Method: GET
Request Parameters:
user_id
: ID of the user to retrieve
Authorization: JWT token required in the Authorization
header
Response Format: JSON object representing the user
Example Request:
curl -H "Authorization: Bearer <your_token>" http://localhost:5000/users/1
Example Response:
{
"id": 1,
"username": "user1",
"email": "user1@example.com",
"profile_picture": "https://example.com/profile1.jpg",
"bio": "This is user 1."
}
PUT/PATCH /users/{user_id}/profile
user_id
: ID of the user to updateusername
(optional)email
(optional)bio
(optional)curl -X PUT http://localhost:5000/users/1 -d '{"username": "updateduser"}'
{
"id": 1,
"username": "updateduser",
"email": "user1@example.com",
"profile_picture": "https://example.com/profile1.jpg",
"bio": "This is user 1."
}
GET /users/{user_id}/timeline
HTTP Method: GET
Request Parameters:
user_id
: ID of the user to retrieve
Authorization: JWT token required in the Authorization
header
Response Format: JSON array of post objects (id, user_id, content, created_at, updated_at, likes_count, comments_count)
Example Request:
curl -H "Authorization: Bearer <your_token>" http://localhost:5000/users/1/timeline
[
{
"id": 1,
"user_id": 1,
"content": "Hello, World!",
"created_at": "2022-01-01T00:00:00",
"updated_at": "2022-01-01T00:00:00",
"likes_count": 0,
"comments_count": 0
}
]
POST /auth/login
username
: The username of the userpassword
: The password of the userqcurl -X POST http://localhost:5000/auth/login -d '{"username": "john_doe", "password": "password123"}'
{
"message": "User john_doe logged in successfully",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcyNzE2Nzk2MCwianRpIjoiZWQ3N2ZmNzQtNTk5Mi00ZjFmLWIyNTQtODA4N2JiMDg2Mjg3IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6NCwibmJmIjoxNzI3MTY3OTYwLCJjc3JmIjoiNmFjMGM0M2EtYTZlMy00ZmI2LTg0MjgtOGEwYTkwOWYzMjBkIiwiZXhwIjoxNzI3MTY4ODYwfQ.TVAA-Up3g6MQUFPsYlQZZ23vBUcYsQTNwID2McvxR7U"
}
POST /auth/register
username
: Username of the new useremail
Email address of the new userpassword
Password of the new userbio
(optional) Bio of the new usercurl -X POST http://localhost:5000/auth/register -d '{"username": "newuser", "email": "newuser@example.com", "password": "password123"}'
{
"message": "User created successfully",
"user": {
"id": 3,
"username": "newuser",
"email": "newuser@example.com"
},
"confirmation_url": "http://localhost:5555/auth/confirm/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcyNzQxNTEzOCwianRpIjoiN2I4YzdhYjgtYjNiZS00YzMxLWJjZjQtODdkZTJjNzBkZjVmIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6NSwibmJmIjoxNzI3NDE1MTM4LCJjc3JmIjoiMTI4ZDMyYzItNDc5Ni00MTYxLTk2ZGEtODAzYmZkZmMxMjQzIiwiZXhwIjoxNzI3NDE2MDM4fQ.RDPX_RIAJ7MoM3F103Q8cYS_8v_6SNMbaw9c3GI_9yY"
}
POST /auth/confirm/{token}
token
: Token to confirm the user's email addresscurl -X POST http://localhost:5000/auth/confirm/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcyNzQxNTEzOCwianRpIjoiN2I4YzdhYjgtYjNiZS00YzMxLWJjZjQtODdkZTJjNzBkZjVmIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6NSwibmJmIjoxNzI3NDE1MTM4LCJjc3JmIjoiMTI4ZDMyYzItNDc5Ni00MTYxLTk2ZGEtODAzYmZkZmMxMjQzIiwiZXhwIjoxNzI3NDE2MDM4fQ.RDPX_RIAJ7MoM3F103Q8cYS_8v_6SNMbaw9c3GI_9yY
- **Example Response:**
```json
{
"message": "Email confirmed successfully"
}
GET /auth/forgot-password
?user_id
: ID of the user to reset the passwordcurl -X GET http://localhost:5000/auth/forgot-password?user_id=1
{
"message": "Password reset link created. Normally this would be sent to your email. For the purposes of this assignment, the link will be displayed here.",
"reset_url": "http://localhost:5000/auth/reset-password/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcyNzQxNTEzOCwianRpIjoiN2I4YzdhYjgtYjNiZS00YzMxLWJjZjQtODdkZTJjNzBkZjVmIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6NSwibmJmIjoxNzI3NDE1MTM4LCJjc3JmIjoiMTI4ZDMyYzItNDc5Ni00MTYxLTk2ZGEtODAzYmZkZmMxMjQzIiwiZXhwIjoxNzI3NDE2MDM4fQ.RDPX_RIAJ7MoM3F103Q8cYS_8v_6SNMbaw9c3GI_9yY"
}
PUT/PATCH /auth/reset-password/{token}
token
: (In URL) Token to reset the user's passwordpassword
: New password for the user.curl -X http://localhost:5000/auth/reset-password/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcyNzQxNTEzOCwianRpIjoiN2I4YzdhYjgtYjNiZS00YzMxLWJjZjQtODdkZTJjNzBkZjVmIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6NSwibmJmIjoxNzI3NDE1MTM4LCJjc3JmIjoiMTI4ZDMyYzItNDc5Ni00MTYxLTk2ZGEtODAzYmZkZmMxMjQzIiwiZXhwIjoxNzI3NDE2MDM4fQ.RDPX_RIAJ7MoM3F103Q8cYS_8v_6SNMbaw9c3GI_9yY
{
"message": "Password reset successful",
"user": {
"id": 1,
"username": "john_doe",
"email": "john@example.com",
"bio": "I like trainZ",
"is_admin": false,
"is_confirmed": true,
"created_at": "2022-02-22T20:30:00.000000"
}
}
PUT/PATCH /auth/change-password
old_password
: Old password for the user.new_password
: New password for the user.Authorization
headercurl -X http://localhost:5000/auth/change-password -H "Authorization: Bearer <your_token>" -d '{"old_password": "old_password", "new_password": "new_password"}'
{
"message": "Password changed successfully",
"user": {
"id": 1,
"username": "john_doe",
"email": "john@example.com",
"bio": "I like trainZ",
"is_admin": false,
"is_confirmed": true,
"created_at": "2022-02-22T20:30:00.000000"
}
}
DELETE /auth/unregister
user_id
: ID of the user to deleteAuthorization
headercurl -X DELETE http://localhost:5000/users/1 -H "Authorization: Bearer <your_token>"
GET /posts
page
(optional): Page number for paginationper_page
(optional): Number of posts per pageAuthorization
header, Administration
permissions are also required to access this endpoint.curl http://localhost:5000/posts -H "Authorization: Bearer <your_token>"
[
{
"id": 1,
"user_id": 1,
"content": "This is a post.",
"created_at": "2023-12-31T23:59:59Z",
"updated_at": "2023-12-31T23:59:59Z",
"likes_count": 0,
"comments_count": 0
},
{
"id": 2,
"user_id": 2,
"content": "Another post.",
"created_at": "2023-12-31T23:59:58Z",
"updated_at": "2023-12-31T23:59:58Z",
"likes_count": 1,
"comments_count": 2
}
]
GET /posts/{post_id}
post_id
: ID of the post to retrieveAuthorization
headercurl http://localhost:5000/posts/1 -H "Authorization: Bearer <your_token>"
{
"id": 1,
"user_id": 1,
"content": "This is a post.",
"created_at": "2023-12-31T23:59:59Z",
"updated_at": "2023-12-31T23:59:59Z",
"likes_count": 0,
"comments_count": 0
}
POST /posts
content
Authorization
headercurl -X POST http://localhost:5000/posts -H "Authorization: Bearer <your_token>" -d '{"content": "This is a new post."}'
{
"id": 3,
"user_id": 1,
"content": "This is a new post.",
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-01-01T00:00:00Z",
"likes_count": 0,
"comments_count": 0
}
PUT / PATCH /posts/{post_id}
post_id
: ID of the post to updatecontent
curl -X PUT http://localhost:5000/posts/1 -H "Authorization: Bearer <your_token>" -d '{"content": "Updated post content."}'
{
"id": 1,
"user_id": 1,
"content": "Updated post content.",
"created_at": "2023-12-31T23:59:59Z",
"updated_at": "2024-01-01T00:00:00Z",
"likes_count": 0,
"comments_count": 0
}
DELETE /posts/{post_id}
post_id
: ID of the post to deletecurl -X DELETE http://localhost:5000/posts/1 -H "Authorization: Bearer <your_token>"
?page
: (Optional) Page number of the feed?per_page
: (Optional) Number of posts per pageAuthorization
headercurl http://localhost:5000/feed -H "Authorization: Bearer <your_token>"
[
{
"id": 3,
"title": "Superman Test Post",
"content": "This is a big good juju test post on the ethanc account!\nI like trains!",
"likes_count": 0,
"comments_count": 0,
"created_at": "2024-09-24T18:29:50.058701",
"updated_at": "2024-09-24T18:29:58.624499",
"author": {
"id": 4,
"username": "ethanc"
},
"likes": [],
"comments": []
},
{
"id": 2,
"title": "Another Post",
"content": "This is my second post. Created by the User",
"likes_count": 1,
"comments_count": 1,
"created_at": "2024-09-24T05:00:18.568652",
"updated_at": null,
"author": {
"id": 2,
"username": "user"
},
"likes": [
{
"id": 1,
"user_id": 1
}
],
"comments": [
{
"id": 1,
"user_id": 1,
"content": "This is a comment on the second post.",
"created_at": "2024-09-24T05:00:18.569052",
"updated_at": null
}
]
},
{
"id": 1,
"title": "Hello, World!",
"content": "This is my first post. Created by the Admin!",
"likes_count": 2,
"comments_count": 2,
"created_at": "2024-09-24T05:00:18.568466",
"updated_at": null,
"author": {
"id": 1,
"username": "admin"
},
"likes": [
{
"id": 2,
"user_id": 2
},
{
"id": 3,
"user_id": 4
}
],
"comments": [
{
"id": 2,
"user_id": 2,
"content": "This is a comment on the first post.",
"created_at": "2024-09-24T05:00:18.569164",
"updated_at": null
},
{
"id": 4,
"user_id": 4,
"content": "This is a big good juju test comment on the first post!",
"created_at": "2024-09-24T18:21:22.664342",
"updated_at": null
}
]
}
]
?page
: (Optional) Page number of the feed?per_page
: (Optional) Number of posts per pageAuthorization
headercurl http://localhost:5000/feed/following -H "Authorization: Bearer <your_token>"
[
{
"id": 3,
"title": "Superman Test Post",
"content": "This is a big good juju test post on the ethanc account!\nI like trains!",
"likes_count": 0,
"comments_count": 0,
"created_at": "2024-09-24T18:29:50.058701",
"updated_at": "2024-09-24T18:29:58.624499",
"author": {
"id": 4,
"username": "ethanc"
},
"likes": [],
"comments": []
},
{
"id": 2,
"title": "Another Post",
"content": "This is my second post. Created by the User",
"likes_count": 1,
"comments_count": 1,
"created_at": "2024-09-24T05:00:18.568652",
"updated_at": null,
"author": {
"id": 2,
"username": "user"
},
"likes": [
{
"id": 1,
"user_id": 1
}
],
"comments": [
{
"id": 1,
"user_id": 1,
"content": "This is a comment on the second post.",
"created_at": "2024-09-24T05:00:18.569052",
"updated_at": null
}
]
},
{
"id": 1,
"title": "Hello, World!",
"content": "This is my first post. Created by the Admin!",
"likes_count": 2,
"comments_count": 2,
"created_at": "2024-09-24T05:00:18.568466",
"updated_at": null,
"author": {
"id": 1,
"username": "admin"
},
"likes": [
{
"id": 2,
"user_id": 2
},
{
"id": 3,
"user_id": 4
}
],
"comments": [
{
"id": 2,
"user_id": 2,
"content": "This is a comment on the first post.",
"created_at": "2024-09-24T05:00:18.569164",
"updated_at": null
},
{
"id": 4,
"user_id": 4,
"content": "This is a big good juju test comment on the first post!",
"created_at": "2024-09-24T18:21:22.664342",
"updated_at": null
}
]
}
]
GET /posts/{post_id}/comments
post_id
: ID of the post to get comments forpage
(optional): Page number for paginationper_page
(optional): Number of comments per pagecurl http://localhost:5000/posts/1/comments -H "Authorization: Bearer <your_token>"
[
{
"id": 1,
"user_id": 2,
"post_id": 1,
"content": "Great post!",
"created_at": "2024-01-01T00:01:00Z",
"updated_at": "2024-01-01T00:01:00Z"
},
{
"id": 2,
"user_id": 3,
"post_id": 1,
"content": "I agree.",
"created_at": "2024-01-01T00:02:00Z",
"updated_at": "2024-01-01T00:02:00Z"
}
]
POST /posts/{post_id}/comments
post_id
: ID of the post to comment oncontent
curl -X POST http://localhost:5000/posts/1/comments -H "Authorization: Bearer <your_token>" -d '{"content": "This is a comment."}'
{
"id": 3,
"user_id": 1,
"post_id": 1,
"content": "This is a comment.",
"created_at": "2024-01-01T00:03:00Z",
"updated_at": "2024-01-01T00:03:00Z"
}
PUT /posts/{post_id}/comments/{comment_id}
comment_id
: ID of the comment to updatecontent
(optional)curl -X PUT http://localhost:5000/comments/1 -H "Authorization: Bearer <your_token>" -d '{"content": "Updated comment."}'
{
"id": 1,
"user_id": 2,
"post_id": 1,
"content": "Updated comment.",
"created_at": "2024-01-01T00:01:00Z",
"updated_at": "2024-01-01T00:03:00Z"
}
DELETE /posts/{post_id}comments/{comment_id}
comment_id
: ID of the comment to deletecurl -X DELETE http://localhost:5000/comments/1 -H "Authorization: Bearer <your_token>"
POST /posts/{post_id}/like
post_id
: ID of the post to likecurl -X POST http://localhost:5000/posts/1/likes -H "Authorization: Bearer <your_token>"
DELETE /posts/{post_id}/like
post_id
: ID of the post to unlikecurl -X DELETE http://localhost:5000/posts/1/likes -H "Authorization: Bearer <your_token>"
{
"message": "Post unliked successfully"
}
GET /posts/{post_id}/likes
curl http://localhost:5000/posts/1/likes -H "Authorization: Bearer <your_token>"
{
"likes": [
{
"id": 1,
"user_id": 1,
"post_id": 1
},
{
"id": 2,
"user_id": 2,
"post_id": 1
}
]
}
POST /users/{user_id}/follow
user_id
: ID of the user to followcurl -X POST http://localhost:5000/users/2/follow
DELETE /users/{user_id}/follow
user_id
: ID of the user to unfollowcurl -X DELETE http://localhost:5000/users/2/follow
Remember to replace http://localhost:5000
with the actual URL of your API.
pip
package managerInstall PostgreSQL:
brew install postgresql
Create a PostgreSQL user and database:
CREATE USER your_username WITH PASSWORD 'your_password';
CREATE DATABASE social_media_api;
GRANT ALL PRIVILEGES ON DATABASE social_media_api TO your_username;
\connect social_media_api
GRANT USAGE ON SCHEMA public TO your_username;
Clone the repository:
git clone https://github.com/finneh4249/t2a2-api-application.git
Create a virtual environment:
python -m venv venv
source venv/bin/activate # On Windows: venv\bin\activate.ps1
Install dependencies:
pip install -r requirements.txt
.env.example
file with your database connection details, including the username, password, database name, and host.
Rename the .env.example
file to .env
. flask cli db_create
This command will create all the tables in the database, and seed it with default values.
flask run
The API implements robust error handling to provide informative feedback to clients in case of exceptions or unexpected situations. When an error occurs, the API returns a JSON response with a descriptive error message and an appropriate HTTP status code.
@user_controller.route('/users/<user_id>')
def get_user(user_id):
user = User.query.get(user_id)
if user is None:
return {"message": "User not found"}, 404
In this example, the code attempts to retrieve the user with the specified user_id. If the user is not found, a 404 Not Found
error is raised.
Other errors that may commonly occur during the execution of the code are handled in the same way, for example, a 401 Unauthorized
error is returned if the user is not the owner of the requested resource.
With the exception of a Marshamallow Validation Error
, if any other exception occurs, a generic 500 Internal Server Error
is returned with an error message.
The CLI commands are provided for convenience. You can use them to create or drop the database, and create a new user, or admin user.
flask cli db_create # Creates all tables in the database.
flask cli db_drop # Drops all tables in the database.
flask cli create_user <username> <email> <password> <bio> [--admin] # Creates a user, use the --admin flag to create an admin user.
flask cli delete_user <username> # Deletes the selected user from the database.