Skip to content

Commit

Permalink
feat: add Postgresql && routes user
Browse files Browse the repository at this point in the history
  • Loading branch information
PedroFnseca committed Aug 30, 2023
1 parent e70328e commit 2a4bf9b
Show file tree
Hide file tree
Showing 14 changed files with 283 additions and 58 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
CC = gcc
CFLAGS = -Wall -g
LIBS = -lmicrohttpd
CFLAGS = -Wall -g -I/usr/include/postgresql
LIBS = -lmicrohttpd -L/usr/lib/x86_64-linux-gnu -lpq
SRCDIR = src
BINDIR = bin

Expand Down
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ I've deliberately chosen the C programming language for this project to challeng

My primary goal was to gain a deeper understanding of how APIs function under the hood and how they communicate with the underlying operating system. The C language is exceptionally suited for this purpose due to its low-level nature and direct interaction with hardware and system resources.

## Postgres Database
In this project, I'm using a Postgres database to store user information. The database is hosted on cloud in the [supabase](https://supabase.com/), and the server communicates with it using the [libpq](https://www.postgresql.org/docs/9.1/libpq.html) library.

## How to Run the Server

### Prerequisites:
Expand All @@ -29,7 +32,11 @@ My primary goal was to gain a deeper understanding of how APIs function under th
cd rest-api-C
```

3. Running the Server:
3. Update the Database Credentials
- Open the `pg.h` file in the `src` directory.
- Update the variables with your database credentials.

4. Running the Server:
- Using Docker:
```bash
docker build -t rest-api-c .
Expand All @@ -45,15 +52,14 @@ My primary goal was to gain a deeper understanding of how APIs function under th
./bin/main
```

4. Access the API at http://localhost:8080
5. Access the API at http://localhost:8080

## Endpoints (Work in Progress)

This RESTful web server provides the following endpoints (please note that this section is a work in progress):

- **GET /:** Welcome message and basic information about the API (not yet implemented).
- **GET /users:** Retrieve a list of users (not yet implemented).
- **GET /users/{id}:** Retrieve detailed information about a specific user (not yet implemented).
- **GET /users:** Retrieve a list of users.
- **GET /users/{id}:** Retrieve detailed information about a specific user.
- **POST /users:** Create a new user (not yet implemented).
- **PUT /users/{id}:** Update information for a specific user (not yet implemented).
- **DELETE /users/{id}:** Delete a user (not yet implemented).
Expand Down
Binary file removed bin/handler.o
Binary file not shown.
Binary file modified bin/main
Binary file not shown.
Binary file removed bin/main.o
Binary file not shown.
2 changes: 1 addition & 1 deletion build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ set -e

mkdir -p bin

gcc -o bin/main src/main.c -lmicrohttpd
gcc -o bin/main src/main.c -lmicrohttpd -I/usr/include/postgresql -L/usr/lib/x86_64-linux-gnu -lpq
2 changes: 1 addition & 1 deletion dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ FROM gcc:latest

# Instale as dependências necessárias
RUN apt-get update && \
apt-get install -y libmicrohttpd-dev make nginx
apt-get install -y libmicrohttpd-dev make nginx libpq-dev

RUN mkdir -p /app

Expand Down
49 changes: 35 additions & 14 deletions src/handler.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
#include "response_builder.h"
#include "user_handler.h"
#include <setjmp.h>

jmp_buf exceptionBuffer;

#define TRY if (setjmp(exceptionBuffer) == 0)
#define CATCH else

void log_api(const char *url, const char *method) {
printf("[%s] %s\n", method, url);
Expand All @@ -12,29 +19,43 @@ enum MHD_Result default_handler(void *cls, struct MHD_Connection *connection, co
int ret;

struct MHD_Response *response;
HTTP_status status;
char *response_body = NULL;
HTTP_response response_api;

log_api(url_str, method_str);

if (strcmp(url_str, "/") == 0) {
response_body = simple_message("Hello, world!");
status = OK;
} else if (strcmp(url_str, "/users") == 0) {
// mandar para modulo de usuarios
response_body = simple_message("Sorry, this endpoint is not implemented yet");
status = OK;
} else {
response_body = simple_message("Not found");
status = NOT_FOUND;
TRY {
if (strcmp(url_str, "/") == 0) {
response_api = (HTTP_response){
.body = simple_message("Hello world!"),
.status = OK
};
}

else if (is_valid_route(url_str, "/users")) {
response_api = user_router(url_str, method_str, upload_data);
}

else {
response_api = (HTTP_response){
.body = simple_message("Not found"),
.status = NOT_FOUND
};
}
} CATCH {
response_api = (HTTP_response){
.body = simple_message("Internal server error"),
.status = INTERNAL_SERVER_ERROR
};

printf("Internal server error");
}

response = HTTP_build_response_JSON(response_body, status);
response = HTTP_build_response_JSON(response_api.body);

if (!response)
return MHD_NO;

ret = MHD_queue_response(connection, status, response);
ret = MHD_queue_response(connection, response_api.status, response);
MHD_destroy_response(response);

return ret;
Expand Down
18 changes: 9 additions & 9 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@
#define PORT 8080

int main() {
printf("Starting server on port %d\n", PORT);
printf("Starting server on port %d\n", PORT);

struct MHD_Daemon *daemon;
struct MHD_Daemon *daemon;

daemon = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, PORT, NULL, NULL,
&default_handler, NULL, MHD_OPTION_END);
if (!daemon)
return 1;
daemon = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, PORT, NULL, NULL,
&default_handler, NULL, MHD_OPTION_END);
if (!daemon)
return 1;

getchar();
getchar();

MHD_stop_daemon(daemon);
MHD_stop_daemon(daemon);

return 0;
return 0;
}
85 changes: 85 additions & 0 deletions src/pg.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libpq-fe.h>

#define database "*****"
#define user "*****"
#define password "*****"
#define host "*****"
#define port "*****"

char *executeQueryToJson(const char *query);

char *formatResultAsJson(PGresult *result);

char *executeQueryToJson(const char *query) {
PGconn *conn = PQconnectdb(
"user=" user
" password=" password
" dbname=" database
" host=" host
" port=" port
);

if (PQstatus(conn) != CONNECTION_OK) {
fprintf(stderr, "Connection to database failed: %s", PQerrorMessage(conn));
PQfinish(conn);
return NULL;
}

PGresult *result = PQexec(conn, query);

if (PQresultStatus(result) != PGRES_TUPLES_OK) {
fprintf(stderr, "Query execution failed: %s", PQerrorMessage(conn));
PQclear(result);
PQfinish(conn);
return NULL;
}

char *jsonResult = formatResultAsJson(result);

PQclear(result);
PQfinish(conn);

return jsonResult;
}

char *formatResultAsJson(PGresult *result) {
int numFields = PQnfields(result);
int numRows = PQntuples(result);

// Calcula o tamanho total necessário para a string JSON -> para não estourar o buffer
int totalSize = numRows * (2 + numFields * 256) + numRows - 1 + 3;
// Cada campo contribui com pelo menos 256 caracteres, 2 para aspas e 1 para vírgula
// numRows - 1 é para as vírgulas entre objetos JSON
// 3 é para as chaves finais e terminador de string

char *json = (char *)malloc(totalSize);
json[0] = '\0';

if (numRows == 0) {
strcat(json, "[]");
return json;
}

for (int i = 0; i < numRows; ++i) {
strcat(json, "{");
for (int j = 0; j < numFields; ++j) {
if (j > 0) strcat(json, ",");
char *fieldName = PQfname(result, j);
char *fieldValue = PQgetvalue(result, i, j);

strcat(json, "\"");
strncat(json, fieldName, totalSize - strlen(json) - 5);
strcat(json, "\":\"");
strncat(json, fieldValue, totalSize - strlen(json) - 5);
strcat(json, "\"");
}
strcat(json, "}");
if (i < numRows - 1) strcat(json, ",");
}

return json;
}

4 changes: 2 additions & 2 deletions src/response_builder.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#include <microhttpd.h>
#include "types.h"
#include "utils.h"

struct MHD_Response *HTTP_build_response_JSON(const char *message, HTTP_status status) {
struct MHD_Response *HTTP_build_response_JSON(const char *message) {
struct MHD_Response *response;

response = MHD_create_response_from_buffer(strlen(message), (void *)message, MHD_RESPMEM_PERSISTENT);
Expand Down
24 changes: 0 additions & 24 deletions src/types.h

This file was deleted.

93 changes: 93 additions & 0 deletions src/user_handler.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#include "pg.h"
#include <string.h>
#include <stdio.h>

HTTP_response get_all(const char *url) {
const char *query = "SELECT * FROM users";

char *result = executeQueryToJson(query);

if (result == NULL) {
return (HTTP_response){
.body = simple_message("Internal server error"),
.status = INTERNAL_SERVER_ERROR
};
}

return (HTTP_response){
.body = result,
.status = OK
};
}

HTTP_response get_one(const char *user_id) {
char query[64];
snprintf(query, sizeof(query), "SELECT * FROM users WHERE id = %s", user_id);

char *result = executeQueryToJson(query);

if (result == NULL) {
return (HTTP_response){
.body = simple_message("Internal server error"),
.status = INTERNAL_SERVER_ERROR
};
}

return (HTTP_response){
.body = result,
.status = OK
};
}

HTTP_response create(const char *body) {
return (HTTP_response){
.body = simple_message("create"),
.status = OK
};
}

HTTP_response update(const char *user_id, const char *body) {
return (HTTP_response){
.body = simple_message("update"),
.status = OK
};
}

HTTP_response drop(const char *user_id) {
return (HTTP_response){
.body = simple_message("drop"),
.status = OK
};
}

HTTP_response user_router(const char *url, const char *method, const char *body){
char *user_id = strstr(url, "/users/");
if (user_id != NULL) {
user_id += strlen("/users/");
}

if (is_valid_method(method, "GET")) {
if(user_id == NULL){
return get_all(url);
} else {
return get_one(user_id);
}
}

if (is_valid_method(method, "POST")) {
return create(body);
}

if (is_valid_method(method, "PUT")) {
return update(user_id, body);
}

if (is_valid_method(method, "DELETE")) {
return drop(user_id);
}

return (HTTP_response){
.body = simple_message("Not implemented"),
.status = NOT_IMPLEMENTED
};
}
Loading

0 comments on commit 2a4bf9b

Please sign in to comment.