Implementation of a twitter-like RESTAPI. The endpoints permits to register a user, login, change credentials, post read and delete a 'chirp', aka tweet.
In the app there are implemented endopints in order to register and user, change its credentials post/delete and list chirps.
A fittitious webhook is implemented that changes the status of the account to the premium version chirpy red.
A system of authentication via JWTs is implemented (with an expire time of max 24 hours), with the relative refresh tokens (with an expire time of 60 days). The refresh token is rotated every time the user credentials are changed or the JWT are refreshed and they can be revoked with the apposite endpoint. The secret key for encripting the JWTs is stored in a .env
file in the root of the repo.
The webhook endpoints authorization is implemented via an API key that is stored in a .env
file in the root of the repo and the users passwords are stored in the database as hashed strings.
The database is implemented using postgres with Go code generated by sqlc and the database migrations handled by goose.
If an HTTP fails a response is given in the form
{
"error": "<error message with information relative to what happened and where>",
"status code": 1 # whatever code may be
}
Internal errors, e.g. fails to parse json, read some file, use some functions etc. are reported with the status code 500
and the relative message while more specific errors are listed in the endpoints below.
type User struct {
Id uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Email string `json:"email"`
HashedPassword string `json:"hashed_password"`
IsChirpyred bool `json:"is_chirpy_red"`
}
type Chirp struct {
Id uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Body string `json:"body"`
UserId uuid.UUID `json:"user_id"`
}
git clone https://github.com/niccolot/Chirpy
cd Chirpy
# for hashing functions
go getgolang.org/x/crypto
# for env variables functions
go getgithub.com/joho/godotenv
# for jwt functions
go get github.com/golang-jwt/jwt/v5
# postgres go driver
go get github.com/lib/pq
# for uuids
go get github.com/google/uuid
# for database migrations
go install github.com/pressly/goose/v3/cmd/goose@latest
# for installing postgres, choose whatever password you want
sudo apt install postgresql postgresql-contrib
sudo passwd postgres
The dependencies can be installed by running the apposite script
cd scripts
chmod +x install_dependencies.sh
./install_dependencies.sh
cd ..
In order to function the server has to read some environment variables, the POLKA_API_KEY
which mimics the api key usually provided by third party companies and the JWT_SECRET
that is used for the enription of the JWTs. This is done via the create_env_vars.sh
script
cd scripts
chmod +x create_env_vars.sh
./create_env_vars.sh
cd ..
After having created the .env
file one can load the environment variables with
source .env
and start the server with the provided script that before compiling the project checks if postgres is active
cd scripts
chmod +x start.sh
./start.sh
cd ..
Once the server is started one can play with it sending HTTP request from a separate terminal watching the responses. One can use the linux curl
command or the VScode extention thunder client that gives a GUI for cheking the functioning of servers.
-
POST /api/users
Allows to register a new user
{ "password": "1234", "email": "[email protected]" }
{ "id": "4b15da34-2729-444e-bff6-dc95d9c7a101", "created_at": "2024-10-03T07:40:53.137648Z", "updated_at": "2024-10-03T07:40:53.137648Z", "email": "[email protected]", "is_chirpy_red": false # no premium subscription by default }
Status code:
201
-
PUT /api/users
Allows to change user credentials. After this the refresh token is rotated for safety.
The header must contain the users JWT
Authorization: "Bearer <jwt>"
{ "password": "newpassword", "email": "[email protected]" }
{ "id": "4b15da34-2729-444e-bff6-dc95d9c7a101", "created_at": "2024-10-03T07:40:53.137648Z", "updated_at": "2024-10-04T07:40:53.137648Z", "email": "[email protected]", "is_chirpy_red": false }
Status code:
200
If the JWT is invalid the request is denied
- Message:
invalid token
- Status code:
401
- Message:
-
DELETE /api/users/{id}
Allows to delete the user correspoinding to
{id}
. This endpoint will also delete every chirp associated with that user.The header must contain the users JWT
Authorization: "Bearer <jwt>"
Status code:
204
If the
id
field of the user in case is different from theid
associated with the JWT the request is denied- Message:
invalid user
- Status code:
403
If the users JWT is invalid the request is denied
- Message:
invalid token
- Status code:
403
- Message:
-
POST /api/login
Allows a user to login. The predefined expiration time for the JWTs is 1 hour
{ "password": "1234", "email": "[email protected]", }
The response will contain the user informations
{ "id": "4b15da34-2729-444e-bff6-dc95d9c7a101", "created_at": "2024-10-03T07:40:53.137648Z", "updated_at": "2024-10-04T07:40:53.137648Z", "email": "[email protected]", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c", "refresh_token": "7b6c3b2e16b36e56d24f5d4f5c0229c72ce8379a077c010c2d8c796362f6610c", "is_chirpy_red": false }
If the password in the request do not correspond to the hashed password in the database the request is denied
- Message:
unathorized access
- Status code:
401
If the email is not in the databse the request is denied
- Message:
user <email> not found
- Status code:
404
- Message:
-
POST /api/chirps
Allows to post a chirp
The header has to contain the users JWT
Authorization: "Bearer <jwt>"
{ "body": "chirp text goes here" }
{ "id": "4b15da34-2729-444e-bff6-dc95d9c7a101", "created_at": "2024-10-03T07:40:53.137648Z", "updated_at": "2024-10-03T07:40:53.137648Z", "body": "chirp text goes here", "author_id": "6520a0cd-6061-41ce-a38f-ba5631758fc7" }
If the JWT is invalid the request is denied
- Message:
invalid token
- Status code:
403
- Message:
-
GET /api/chirps
Allows to list every chirp in the database by returning an array. It is possible to sort the chirps in ascending (default) or descending order (according to the
chirp_id
) and retrieve chirps belonging only to a certain user by using queries in the URL.Examples of valid URLs
GET http://localhost:8080/api/chirps?sort=asc&author_id=2 GET http://localhost:8080/api/chirps?sort=asc GET http://localhost:8080/api/chirps?sort=desc GET http://localhost:8080/api/chirps
Using
GET http://localhost:8080/api/chirps?sort=asc&author_id=4e3936cb-09ae-44e2-a98c-303322c4f2f3
[ { "id":"6520a0cd-6061-41ce-a38f-ba5631758fc7", "body":"Gale!", "author_id":"4e3936cb-09ae-44e2-a98c-303322c4f2f3" }, { "id":"d97e9629-e5c8-46c1-a099-0d6c2615f248", "body":"Cmon Pinkman", "author_id":"4e3936cb-09ae-44e2-a98c-303322c4f2f3" }, { "id":"1a1332be-91e5-4c58-8b2e-89f08f212e98", "body":"Darn that fly, I just wanna cook", "author_id":"4e3936cb-09ae-44e2-a98c-303322c4f2f3" } ]
-
GET /api/chirps/{id}
Retrieves only the chirp with id
id
GET http://localhost:8080/api/chirps/4b15da34-2729-444e-bff6-dc95d9c7a101
{ "id":"4b15da34-2729-444e-bff6-dc95d9c7a101", "body":"I'm the one who knocks!", "author_id":"1658ddd3-2a4d-4b2a-a8a9-48f52b09cc62" }
-
PUT /api/chirps/{id}
Allows to modify a chirp.
Authorization: "Bearer <jwt>"
{ "id": "4b15da34-2729-444e-bff6-dc95d9c7a101", "Body": "new text" }
{ "id": "4b15da34-2729-444e-bff6-dc95d9c7a101", "created_at": "2024-10-03T07:40:53.137648Z", "updated_at": "2024-10-04T07:40:53.137648Z", "body": "new text" }
Status code:
200
If the JWT is invalid the request is denied
- Message:
invalid token
- Status code:
401
- Message:
-
POST /api/refresh
Allows to refresh the jwt. After the jwt has been changed the refresh token is rotated for safety.
The header of the request has to contain the refresh token (that will be changed at the end)
Authorization: "Bearer <refresh_token>"
{ "token": "new.issued.jwt", "refresh_token": "new_rotated_refresh_token" }
If the refresh token is not found in the databse the request is denied
- Message:
refresh token does not exists
- Status code:
401
- Message:
-
POST /api/revoke
Allows to revoke the refresh token. After this request both the refresh token and the expire time are substituded by an empty string
""
The header of the request has to contain the refresh token
Authorization: "Bearer <refresh_token>"
Status code:
204
If the refresh token is not found in the databse the request is denied
- Message:
refresh token does not exists
- Status code:
401
- Message:
-
DELETE /api/chirps/{chirpId}
Allows to delete the chirp corresponding to
chirpId
The header must contain the users JWT
Authorization: "Bearer <jwt>"
Status code:
204
If the
author_id
field of the chirp in case is different from theuser_id
associated with the JWT the request is denied- Message:
invalid user
- Status code:
403
If the users JWT is invalid the request is denied
- Message:
invalid token
- Status code:
403
- Message:
-
POST /api/polka/webhooks
Allows to post a (fictitious) payment request via a webhook in order to update the account to chirpy red
The header of the request has to contain the Polka API key
Authorization: "ApiKey <apikey>"
{ # a different string will fail to update the account "event": "user.upgraded", "data": { "user_id": "1a1332be-91e5-4c58-8b2e-89f08f212e98" } }
Status code:
204
If the API key is invalid the request is denied
- Message:
invalid api key
- Status code:
401
If the user is not in the databse the request is denied
- Message:
user_id <user_id> not found
- Status code:
404
- Message:
-
GET /api/healthz
Allows to check if the server is online
Status code:
200
Body:OK
-
GET /admin/metrics/
Allows to see the number of visits to the site by rendering the HTML page
index_admin.html
-
/admin/reset
Deletes all entries in the database
-
app/*
Renders the
index.html
file